Um Sistema de Animação baseado em Movimento Capturado

 

Descrição Técnica

 

 

Fernando Wagner Serpa Vieira da Silva

nando@visgraf.impa.br

 

IMPA - Instituto de Matemática Pura e Aplicada

Laboratório VISGRAF

 

1 Introdução

 

A utilização da tecnologia de captura de movimento no campo de Animação por Computador introduziu um enorme salto no que diz respeito à qualidade e velocidade de produção.

Animar objetos articulados com um nível de complexidade alta era uma tarefa extremamente árdua, pois com as técnicas existentes até então havia a necessidade de uma grande habilidade e esforço do animador. O resultado era lento e nem sempre a qualidade era a esperada. Utilizando-se a técnica de Motion Capture (MC), certas nuances do movimento são introduzidas, proporcionando um realismo que dificilmente seria atingido através de técnicas tradicionais como keyframing e simulação.

Atualmente, existe uma grande discussão sobre as vantagens e desvantagens do uso de MC na produção de animações, quando comparado às técnicas existentes anteriormente. Bons exemplos podem ser encontrados em [Digital 96][FXF].

Os sistemas profissionais de captura acompanham softwares que manipulam os dados relativos ao movimento capturado, processando-os de forma a eliminar ou reduzir ruídos e ajustar pequenas imperfeições inerentes ao processo. Porém, se tais imperfeições forem muito grandes, ou se o desempenho do performer ficar abaixo do esperado, os softwares não conseguirão resolver o problema e, consequentemente, todo o processo de captura terá que ser refeito.

O objetivo do sistema proposto é explorar a possibilidade de uma manipulação mais profunda de dados provenientes de sistemas de captura de movimento. Bibliotecas de movimentos capturados serão utilizadas para gerar animações complexas, através da combinação/modificação de seus elementos, sem a necessidade de novas sessões de captura e, principalmente, mantendo as características originais de realismo do processo de captura. Além disso, o sistema servirá de plataforma para a pesquisa de novas técnicas de animação e implementação de técnicas já existentes [Witkin 94] [Cohen 96] [Williams 94].

A representação dos movimentos na interface gráfica, de uma forma intuitiva e conceitualmente correta, é o ponto de partida para a construção do sistema. O objetivo é representar os movimentos e as operações como entidades da interface.

Internamente, o sistema foi dividido em três módulos: entrada, processamento e saída. Todos esses módulos são amparados pela interface gráfica, que fornece objetos específicos para cada função do sistema.

No módulo de entrada estão localizadas as rotinas de inicialização do sistema, bem como funções para a interpretação e conversão dos dados "brutos" dos diferentes formatos de MC suportados pelo sistema, para um formato interno mais apropriado. Também se encontram neste módulo rotinas para a leitura da estrutura articulada - o esqueleto do ator virtual, onde serão mapeados os dados relativos aos movimentos capturados.

O módulo de processamento abrange as rotinas de modificação e análise dos movimentos que estão nas bibliotecas que acompanham o sistema. Filtragem, concatenação e blending estão entre as principais operações de movimento disponíveis inicialmente no sistema.

No último módulo, o de saída, estão localizadas as rotinas de gravação da composição feita pelo usuário. Será possível gravar o movimento em vários formatos, compatíveis com diferentes sistemas de captura de movimento disponíveis no mercado [Visgraf 96], e também em formatos adequados para uma visualização direta da animação.

As estruturas de dados que serão utilizadas pelo sistema foram baseadas na representação APJ, proposta por Zeltzer [Zeltzer 88]. Uma adaptação foi introduzida para adequá-las ao uso com dados provenientes de sistemas de captura de movimento.

 

 

2 Descrição do Sistema

 

Nas próximas seções, será feita uma análise das principais estruturas que compõem a arquitetura do sistema, abordando tópicos que vão desde o formato de dados interno até a composição de sua interface.

 

 

2.1 Organização Externa

 

Externamente, o sistema será organizado segundo a estrutura proposta na figura 1. Esta organização segue os padrões utilizados nos principais pacotes de distribuição na rede.

Note a presença do diretório para a biblioteca de definição de esqueletos, onde o usuário poderá adicionar ao longo do tempo diferentes tipos de estruturas articuladas, que serão interpretadas pelo sistema. Além disso, existe um diretório onde estão concentradas as bibliotecas de movimentos capturados, classificadas em diversos temas.

 

 

Figura 1 - Organização em diretórios do Sistema.

 

 

2.2 Estrutura de Dados

 

O primeiro passo na construção de um sistema de animação de objetos articulados é desenvolver uma estrutura de dados que represente adequadamente o estado dos inúmeros graus de liberdade dos componentes que formam o esqueleto.

A estrutura de dados proposta tem como base a representação APJ (Axis Position Joint), que descreve as articulações de uma forma simples e intuitiva. Basicamente, a representação APJ consiste no armazenamento dos seguintes dados:

 

 

Este modelo serviu de base para a formulação de uma estrutura de dados um pouco mais complexa, caracterizada por uma representação das principais entidades manipuladas pelo sistema, em diferentes níveis de detalhamento com relação aos seus sub-componentes (Figura 2).

 

 

Figura 2 - Entidades manipuladas pelo Sistema

 

No mais alto nível encontra-se a estrutura skeleton, que contém a descrição dos limbs (joints + links).

Por sua vez, a estrutura limbs contém informações geométricas que serão utilizadas na visualização do ator, dando-lhe uma aparência mais realista. Além disso, esta estrutura fornece ponteiros para os joints proximal e distal - componentes básicos dos limbs (Figura 3).

 

 

Figura 3 - Componentes de um limb

 

Os joints são as estruturas básicas do esqueleto, e são melhor representados espacialmente por meio de um grafo. Neles, serão mapeados os dados dos movimentos capturados. Consequentemente, a estrutura dos joints deve conter informações sobre seu tipo (prismático ou de revolução), curvas de movimento associadas e ponteiros para os joints vizinhos no grafo.

Por último, foi definida a estrutura motion curves, que representa as curvas de movimento, e contém as informações sobre a posição e orientação dos joints, capturadas ao longo do tempo.

Veja abaixo, em pseudo-código, uma descrição mais detalhada das principais estruturas utilizadas pelo sistema.

 

Estrutura Skeleton

{

string Name; /* identificação da estrutura */

Limbs SkLimbs; /* segmentos da estrutura articulada */

}

 

Estrutura Limbs

{

string Name; /* identificação do segmento */

Geometry LGeom; /* informações geométricas do segmento */

pointer prox, dist; /* ponteiros para os joints proximal / distal */

}

 

Estrutura Joints

{

string Name; /* identificação do joint */

int Jtype; /* tipo do joint */

MotionCurve MotCur; /* curvas de movimento associadas */

pointer next, prev; /* ponteiros para joints vizinhos */

}

 

Estrutura MotionCurves

{

string Label; /* nome/id do movimento */

Info Info; /* estrutura com informações úteis ( nframes, etc) */

/* dados sobre posição/orientação ao longo do tempo */

 

float Xpos[Nframes], Ypos[Nframes], Zpos[Nframes];

float Xang[Nframes], Yang[Nframes], Zang[Nframes];

}

 

 

2.3 Módulos e Funções

 

Internamente, o sistema é dividido em quatro módulos: entrada, processamento, saída e interface. Cada módulo é responsável pela execução de determinadas tarefas, e agrupa as rotinas de acordo com as suas funções. Na figura a seguir, observe o fluxograma dos módulos do sistema.

 

 

Figura 4 - Fluxograma dos módulos do Sistema

 

 

Neste módulo, encontram-se as rotinas responsáveis pela leitura e interpretação dos movimentos das bibliotecas, e também da estrutura articulada que será utilizada pelo sistema.

Cálculos geométricos importantes, mapeamento dos movimentos na estrutura articulada e criação do ator virtual - são algumas das funções de rotinas existentes neste módulo.

 

 

Figura 5 - Fluxograma do Módulo de Entrada

Descrição das principais rotinas deste módulo

 

Function LoadMovement

 

Objetivo: interpretar os movimentos da biblioteca, selecionados pelo usuário. Esta função possuirá um conversor dos formatos profissionais suportados pelo sistema, compatibilizando-os com a estrutura de dados interna do sistema.

 

Parâmetros de Entrada: movimentos da biblioteca selecionados pelo usuário através da GUI.

 

Parâmetros de Saída: dados sobre os movimentos organizados segundo a estrutura de dados interna do sistema.

 

Objeto de Interface Relacionado: Janela principal do Sistema.

 

 

Function OrganiseMotions

 

Objetivo: organizar os movimentos lidos pelo usuário, colocando id’s e posicionando-os no Motion ScrathPad. Os id’s serão úteis, internamente, para referenciar os movimentos na execução das operações.

 

Parâmetros de Entrada: movimentos da biblioteca selecionados pelo usuário através da GUI.

 

Parâmetros de Saída: (ver objetivo).

 

Objeto de Interface Relacionado: MotionScratchPad Window (figura 12).

 

 

Function LoadSkeleton

 

Objetivo: interpretar a estrutura articulada definida nos arquivos da biblioteca de esqueletos pré-definidos. Para cada formato de MC suportado pelo sistema, foi definido um "esqueleto ideal" - um arquivo cujo objetivo é servir de interface entre a estrutura esperada pelo formato de MC lido e a organização interna do sistema, resolvendo assim os problemas de incompatibilidade entre os segmentos do arquivo de movimento e o modelo do ator [Ginsberg 83].

Basicamente, o arquivo de definição do esqueleto contém informações sobre o número de segmentos que o formato de MC escolhido suporta, e um conjunto de relações entre os id’s dos segmentos no arquivo e no modelo do ator. Veja abaixo a listagem do arquivo de definição do esqueleto para o formato de dados BVH (BioVision).

 

# obrigatory header.

Skeleton Definition File - SDF

Authors: Fernando Wagner & Luiz Velho

 

# version info.

Version: 1.0

 

# skeleton type.

SkelType: BVH

 

# total number of segments

SkelSeg: 17

 

# skeleton definition and correspondence.

# first name: internal description; second name: motion file segment id.

# * means no correspondence

SkelDef:

{

hips *

chest Chest

thorax *

neck Neck

neckbone *

head Head

leftupleg LeftHip

leftlowleg LeftKnee

leftfoot LeftAnkle

rightupleg RightHip

rightlowleg RightKnee

rightfoot RightAnkle

leftclavicle LeftCollar

leftuparm LeftShoulder

leftlowarm LeftElbow

lefthand LeftWrist

rightclavicle RightCollar

rightuparm RightShoulder

rightlowarm RightElbow

righthand RightWrist

}

 

Listagem 1 - Arquivo de definição do esqueleto

 

Parâmetros de Entrada: arquivos com a definição do esqueleto, selecionados pelo usuário através da GUI.

 

Parâmetros de Saída: preenchimento das estruturas internas com os dados do esqueleto.

 

Objeto de Interface Relacionado: Joint Structure Window (figura 10).

 

 

Function CalcAngles

 

Objetivo: obter a orientação dos joints do esqueleto, quando o arquivo de movimentos lido pelo usuário não contiver informações angulares.

Os algoritmos utilizados para a obtenção dos ângulos se baseiam em conceitos geométricos, e fornecerão a orientação dos joints em relação ao sistema global de coordenadas, ou em relação aos joints vizinhos na estrutura hierárquica do esqueleto (Figura 6).

 

 

Figura 6 - Etapa do processo de obtenção dos ângulos: projeção nos planos dos joints.

 

Parâmetros de Entrada: informação posicional das curvas de movimentos dos joints.

 

Parâmetros de Saída: orientação dos joints.

 

Objeto de Interface Relacionado: nenhum. Esta rotina será executada internamente, sempre que houver necessidade.

 

 

Function VirtualActor

 

Objetivo: mapear os dados dos movimentos no esqueleto definido pelo usuário. O corpo do ator virtual será criado utilizando como base a estrutura hierárquica definida pelo usuário e os parâmetros geométricos definidos a priori.

 

Parâmetros de Entrada: estrutura do esqueleto, dados sobre o movimento.

 

Parâmetros de Saída: modelo 3D do ator virtual baseado nas informações recebidas.

 

Objeto de Interface Relacionado: nenhum. Esta rotina será executada internamente, sempre que houver necessidade.

 

 

 

O módulo de processamento concentrará as rotinas de análise e manipulação dos movimentos. Uma de suas características principais será a possibilidade de incorporação de novas técnicas e algoritmos ao longo do desenvolvimento do sistema.

A princípio, as ferramentas oferecidas ao usuário possibilitarão a execução de operações de filtragem, concatenação e combinação (blending).

 

Descrição das principais rotinas deste módulo

 

Function Filtering

 

Objetivo: gerenciar a aplicação de filtros no movimento selecionado pelo usuário. Esta rotina concentrará todos os filtros que forem implementados no sistema.

 

Parâmetros de Entrada: movimento + filtro, selecionados pelo usuário na GUI.

 

Parâmetros de Saída: movimento modificado com a aplicação do filtro.

 

Objeto de Interface Relacionado: Filtering Window (figura 14).

 

 

Function Concatenation

 

Objetivo: a partir de uma sequência de movimentos selecionados pelo usuário através da GUI, aplicar uma operação de concatenação, levando em conta os parâmetros definidos na respectiva janela.

 

Parâmetros de Entrada: sequência de movimentos (na verdade, os id’s dos movimentos que farão parte da operação) selecionados pelo usuário, e parâmetros adicionais passados pela interface.

 

Parâmetros de Saída: movimento resultante da composição por concatenação dos movimentos selecionados pelo usuário.

 

Objeto de Interface Relacionado: Concatenation Window (figuras 15 e 16).

 

 

Function Blending

 

Objetivo: a partir de uma sequência de movimentos selecionados pelo usuário através da GUI, aplicar uma operação de fusão (blending), levando em conta os parâmetros definidos na respectiva janela.

 

Parâmetros de Entrada: sequência de movimentos (na verdade, os id’s dos movimentos que farão parte da operação) selecionados pelo usuário, e parâmetros adicionais passados pela interface.

 

Parâmetros de Saída: movimento resultante da composição por blending dos movimentos selecionados pelo usuário.

 

Objeto de Interface Relacionado: Blending Window (figura 17).

 

 

Function ReparametrizeMotion

 

Objetivo: executar a reparametrização de um movimento, lenvando em conta os fatores determinados pelo usuário na GUI.

 

Parâmetros de Entrada: movimento + parâmetros definidos pelo usuário na GUI.

 

Parâmetros de Saída: movimento reparametrizado.

 

Objeto de Interface Relacionado: nenhum. Esta rotina será executada sempre que houver necessidade, por exemplo, durante uma operação de blending ou quando o usuário desejar aumentar o tamanho de um movimento, esticando a barra que o representa.

 

 

 

 

Figura 7 - Fluxograma do Módulo de Processamento

 

 

 

Após a criação da composição pelo usuário, é necessário fornecer um meio de armazenamento da mesma, seja na forma de um arquivo de animação ou colocando-a como um novo arquivo da biblioteca de movimentos. A princípio, o sistema permitirá o armazenamento da composição de três maneiras distinas:

 

  1. formato raster binário (AVI, MPG, etc) para visualização direta da animação.
  2. formato texto compatível com sistemas de motion capture, ou seja, gravação das curvas de movimento dos joints.
  3. formato texto compatível com sistemas profissionais de rendering (ex: povray), para posterior geração dos quadros da animação.

 

Descrição das principais rotinas deste módulo

 

Function SaveComposition

 

Objetivo: gravar a composição feita pelo usuário em um dos diversos formatos de MC profissionais suportados pelo sistema.

 

Parâmetros de Entrada: movimento que representa a composição feita pelo usuário.

 

Parâmetros de Saída: gravação, em disco, da composição no formato escolhido pelo usuário na GUI.

 

Objeto de Interface Relacionado: Janela principal do sistema.

 

 

Function SaveAnimation

 

Objetivo: gravar a animação feita pelo usuário em um formato binário para visualização direta, ou em um formato para post-rendering.

 

Parâmetros de Entrada: movimento que representa a composição feita pelo usuário.

 

Parâmetros de Saída: gravação, em disco, da animação no formato escolhido pelo usuário na GUI.

 

Objeto de Interface Relacionado: Playback Window (figura 9).

 

 

 

 

Figura 8 - Fluxograma do Módulo de Saída

 

 

 

Grande importância foi dada ao desenvolvimento da interface do sistema. Na verdade, ela é considerada ponto cruxial para o entendimento dos conceitos aplicados. O objetivo principal foi criar objetos de interface que melhor representassem as principais entidades manipuladas pelo sistema.

Os movimentos, primitivas básicas do sistema, foram representados por meio de uma barra horizontal, que contém informações sobre o seu tamanho, nome, início e fim. Juntamente com uma régua de graduação em frames, esta representação possibilita uma ótima percepção espacial de cada movimento em relação à composição e a outros movimentos.

As operações entre os movimentos, entidades também importantes, foram igualmente alvo de pesquisa, com o objetivo de construir objetos de interface que tornassem os procedimentos bastante intuitivos.

A seguir, serão apresentados imagens das principais janelas que serão implementadas no sistema, juntamente com uma breve descrição técnica.

 

 

Playback Window

Esta janela tem como objetivo visualizar a composição feita pelo usuário, ou mesmo movimentos individuais da biblioteca. Ferramentas para controle de câmera e iluminação também estão presentes, bem como informações sobre a duração da animação e scrolls para controle interativo do playback.

 

 

Figura 9 - Janela de playback da animação

 

 

Joint Structure Window

 

Nesta janela o usuário terá uma visão global da estrutura articulada, representada através de um grafo. Será possível executar seleções individuais ou em grupo de joints específicos, para a aplicação das operações e/ou visualização de suas curvas de movimento.

 

 

Figura 10- Janela com a representação dos joints do esqueleto (grafo)

 

 

 

 

Joint Curves Window

 

A visualização das curvas de movimento dos joints é importante para a compreensão de certos movimentos. Nesta janela, o usuário poderá observar o comportamento de tais curvas, escolhendo o canal de dados que deseja observar (posição/orientação), e tendo à sua disposição ferramentas para zoom e, no futuro, edição.

 

 

Figura 11 - Janela com a visualização das curvas de movimento de um joint

 

 

Motion ScratchPad Window

 

O conceito de Motion ScratchPad foi introduzido com o objetivo de fornecer ao usuário uma visão global dos movimentos que ele pretende trabalhar, durante a construção da composição.

Estabelecendo uma analogia entre esta idéia e um quebra-cabeças, os movimentos seriam as peças do jogo, que são encaixadas através de operações booleanas para formar a composição final, enquanto que o Motion ScratchPad seria a caixa onde as peças, soltas, estão armazenadas, aguardando o momento de serem utilizadas.

 

 

 

Figura 12 - O repositório de movimentos - Motion ScratchPad

 

 

Movement Window

 

Acionada sempre que um double-click é efetuado sobre um movimento no Motion ScratchPad, esta janela fornece informações detalhadas sobre o mesmo, com a possibilidade de um blayback individual e a ativação/desativação dos joints, representados por sub-faixas na barra que caracteriza o movimento.

 

 

Figura 13 - Janela com a representação do movimento

 

 

Filtering Window

 

Representando a operação de filtragem, esta janela contém uma visão global do movimento, sempre acompanhado por uma barra de graduação em frames. O usuário poderá selecionar a região do movimento onde pretende aplicar o filtro e, então, escolher um dos diversos filtros que acompanham o sistema.

 

 

Figura 14 - Janela representando a operação de filtragem

 

 

Concatenation Window

 

Uma das tarefas mais difíceis ao longo do desenvolvimento da interface foi, sem dúvida, representar coerentemente a operação de concatenação.

Com uma estrutura semelhante à da janela de Motion ScratchPad, a janela de concatenação possui uma série de "trilhos", onde são colocadas as barras que representam os movimentos. Ao carregar os movimentos, os mesmos são, inicialmente, posicionados de modo a alinhar, verticalmente, o final do primeiro movimento com o início do movimento seguinte. Esta é a forma mais simples de concatenação, onde não ocorre nenhum tipo de overlapping dos movimentos (figura 15).

 

 

Figura 15 - Concatenação sem overlapping dos movimentos

 

Associado à extremidade de cada movimento, existe um objeto cuja função é redimensionar (reperametrizar) o movimento, e também servir como um fator de restrição durante a operação pelo usuário. Com o uso destes objetos, é possível estabelecer uma região de overlapping (ou blending) entre dois movimentos consecutivos, representada pela faixa vertical na interface (figura 16).

 

 

Figura 16 - Faixas verticais representando a região de blending na contatenação

 

 

Blending Window

 

Da mesma forma que na concatenação, a janela da operação de blending apresenta um design intuitivo. Nesta janela, os movimentos também são colocados em trihos, só que ordenados, de cima para baixo, com relação ao número de frames. Em outras palavras, movimentos com um maior número de frames ocuparão as primeiras posições.

Esta classificação também funciona como um fator de restrição, e tem como objetivo manter os movimentos menores em uma posição que tornará mais fácil a realização da operação. Será possível também posicionar os movimentos em relação ao "movimento base" - aquele que possui o maior número de frames.

Sob cada movimento existem marcadores especiais, chamados time-markers, cuja função é sincronizar os "momentos-chave" dos movimentos, reparametrizando-os quando necessário.

Um exemplo de utilização dos time-markers seria o blending de dois tipos diferentes de caminhar: para manter a sincronia no movimento resultante, os time-markers seriam utilizados para garantir que os pés do ator virtual tocassem o chão num mesmo momento, nos dois movimentos.

 

 

 

Figura 17 - Janela representando a operação de blending

 

 

 

  1. Linguagem de Animação

 

Está prevista a criação de uma linguagem de animação, que permitirá a execução dos comandos do sistema através da interpretação de um arquivo-texto externo. Desta forma, seria possível criar as composições e salvá-las apenas interpretando este arquivo.

A criação do arquivo contendo a linguagem poderia ser feita de duas formas distintas:

 

  1. O usuário utilizaria um editor de texto comum e, conhecendo a sintaxe da linguagem interna do sistema, escreveria os comandos que formarão a composição.
  2. Durante a utilização do sistema, seriam gravados, automaticamente, em um arquivo-texto, os comandos que o usuário executar na criação da composição. Isto funcionaria como um log dos comandos do usuário, e poderia ser editado posteriormente para modificar o resultado da composição.

 

Veja abaixo a listagem de um exemplo de sintaxe que poderia ser utilizada na liguagem de animação do sistema:

 

proc skeleton

{

load_skel_file vpm.sdf;

set skel_name Joe;

}

 

proc movements

{

load_movement_file (walk.vpm opdoor.vpm run.vpm walk#2.vpm);

set motion_id walk.vpm WALK;

set motion_id opdoor.vpm OPENDOOR;

set motion_id run.vpm run RUN;

set motion_id walk#2.vpm WALK2;

}

 

 

 

 

proc main

{

skeleton;

movements;

 

begin op_concatenate

set motion_first_frame_position (WALK 0);

set motion_first_frame_position (OPENDOOR 80);

set motion_first_frame_position (RUN 140);

concatenate (WALK OPENDOOR RUN);

end op_concatenate

 

set op_result CONC1;

begin op_blending

set motion_first_frame_position (CONC1 0);

set motion_first_frame_position (WALK2 0);

blending (CONC1 WALK2);

end op_blending

 

set op_result FINAL;

 

display FINAL;

}

 

Listagem 2 - Linguagem de animação do sistema

 

3 Referências

 

[Witkin 94] - Witkin, A., Popovic, Z., Motion Waping. In Computer Graphics (SIGGRAPH’94 Proceedings), August 1994, pp. 105-108.

 

[Cohen 96] - Cohen, M., Rose, C., Guenter, B. And Bodenheimer, B., Efficient Generation of Motion Transitions using Spacetime Constraints. In Computer Graphics (SIGGRAPH’96 Proceedings), August 1996, pp. .

 

[Williams 94] - Williams, L., Brudelin, A., Motion Signal Processing. In Computer Graphics (SIGGRAPH’94 Proceedings), August 94, pp. 97-104.

 

[Zeltzer 88] - Zeltzer, D. And Sims, F., "A Figure Editor and Gait Controller for Task Level Animation", SIGGRAPH Course Notes, 4, 164-181, 1988.

 

[Digital 96] - Digital Magic Magazine, January 1997.

 

[FXF] - FX Fighter What? - Motion Capture vs. Keyframing Page.

http://www.im.gte.com/FXF/fxfwhat2.html

 

[Visgraf 96] - Apostila de referências sobre tecnologias de Motion Capture. Projeto de Movimento Capturado. Laboratório VISGRAF. 1996.

 

[Ginsberg 83] - Ginsberg, C., M., "Human Body Motion as Input to an Animated Graphical Display", Master Thesis, Massachusets Institute of Technology, May 1983.

 

[Serpa 96] - Serpa, F. W. S., Introdução ao Motion Capture, Laboratório Visgraf. 1996.