Resolução da Primeira Lista de Exercícios de Fundamentos de Com

Propaganda
Resolução da Primeira Lista de Exercícios de Fundamentos de Computação Gráfica – INF01047
Carlos Eduardo Ramisch – Cartão 134657
Turma B – Prof.ª Luciana Porcher Nedel
Porto Alegre, 03 de abril de 2006.
1 – Para expressar um ponto intermediário em função dos pontos extremos, precisamos primeiro definir quais serão as entradas e saídas da função. Temos que, como entrada, receberemos dois pontos: P0(x0, y0) e P1(x1, y1). A saída será na verdade um conjunto de pontos. Para representar esse conjunto de pontos poderíamos muito bem utilizar uma lista de pontos, porém iremos preferir representar a saída na forma de uma matriz M, que será a representação da tela que irá exibir esse segmento. Nessa matriz, iremos usar a convenção de que “0” é equivalente ao fundo e “1” equivalente ao segmento de reta desenhado. A função em uma pseudolinguagem (semelhante a C) segue abaixo e se baseia na equação paramétrica da reta.
Por convenção:
typedef Ponto struct sPonto{
int x, y;
}
typedef Matriz int[ M ][ N];
int round(float)­ é a função que trunca o valor float acrescido de 0,5, isto é, faz o arredondamento.
Matriz desenhaSegmento( Ponto p0, Ponto p1 ) {
float dx = p1.x – p0.x;
float dy = p1.y – p0.y;
float incT, t;
Matriz m; //Armazena o resultado
limpar( m ); //Preencher a matriz m com valores nulos.
//Decide se a reta está com inclinação maior que 45º ou //menor que 45º em relação ao eixo x. Isso afeta o número //de pontos calculados, isto é, Se a reta for mais //comprida na direção x, o incremento de t (incT) varia //conforme a variação de x, e analogamente para y.
if ( dx > dy ) incT = 1 / dx;
else
incT = 1 / dy;
//Faz o parâmetro t variar de 0 a 1, desenhando o //segmento segundo a lógica de uma equação paramétrica da //reta.
for ( t = 0.0; t <= 1.0; t += incT ) {
float xt = p0.x + t * dx;
float yt = p0.y + t * dy;
m[ round( xt ), round( xy ) ] = 1;
}
return m;
}
2 – Para realizar o recorte da região triangular, é bastante simples. Em primeiro lugar definimos o retângulo, que é composto de 4 pontos (2 seriam suficientes) no formato P(x, y). Eles são dispostos da seguinte maneira:
P0
P1
P2
P3
Os pontos de E que serão exibidos podem ser expressos segundo a seguinte proposição lógica:
∀Q ∈E, ( ( Q.x ≥ P0.x ∧ Q.x ≤ P3.x ∧ Q.y ≥ P3.y ∧ Q.y ≤ P0.y ) → desenha( Q ) )
Isto é, para todo ponto de Q, se ele obedece à restrição de que suas coordenadas sejam internas ao triângulo então ele é desenhado. Note que usamos apenas P0 e P3 na fórmula. A tradução da proposição acima para uma linguagem de programação qualquer é trivial, traduzindo­se as construções da lógica para as correspondentes da linguagem. (por exemplo, ∀ ≡ “for”; → ≡ “if”).
3 – Para um polígono convexo, poderíamos utilizar as normais dos segmentos que definem o polígono. O algoritmo abaixo testa se um ponto qualquer Q pertence ao polígono definido por {P0, P1,..., Pn}*. Este algoritmo deve ser aplicado a cada ponto da matriz que representa a tela (“cada ponto” é o parâmetro q da função) e então para cada valor verdadeiro retornado deve­se colorir o ponto (q). Dessa forma teremos o efeito de preenchimento do polígono.
//o parâmetro ponto[n] é uma lista de pontos p0, p1, ..., pn.
boolean testaDentro( Ponto[n] p, q ) {
int i;
//abaixo representada, operação que adiciona ao fim //da lista de pontos p o primeiro ponto. Isso facilita o *
É importante, nesses algoritmos, saber se a entrada de pontos, que é o conjunto que define o polígono ou face, será percorrida no sentido horário ou anti­horário para o desenho. Isto significa que, dados os pontos, devemos saber em que ordem eles foram nomeados (de P0 a Pn). Nos algoritmos usados, tomaremos por padrão que os pontos são nomeados no sentido horário. Isto significa que, percorrendo os segmentos no sentido horário, teremos primeiro P0, depois P1 e por fim, de Pn voltaremos a P1.
//percorrimento dos pontos, pois precisaremos avaliar //todos os segmentos entre pares de pontos consecutivos, //inclusive o par Pn­P0.
p = p + { p[0] }; for ( i = 0; i <= n; i++ ) {
//note que calculamos aqui o vetor que vai //de p[i+1] até p[i] (e não o contrário).
float dx = p[i].x – p[i+1].x; float dy = p[i].y – p[i+1].y;
//coeficientes da equação expandida da reta, que //facilita os cálculos por seus coeficientes //representarem a normal da reta. Normal = (a,b).
float a = ­dy; float b = dx;
//Agora queremos um vetor que vá de p[i] até q. //v = q – p[i].
Ponto v;
v.x = q.x – p[i].x;
v.y = q.y – p[i].y;
//agora temos os dois vetores e calculamos o produto //escalar para saber sobre o ângulo entre eles.
float produtoEscalar = a * v.x + b * v.y;
if ( produtoEscalar < 0 ) {
//Significa que o ângulo entre os vetores é maior //que 90º, logo, o ponto está fora do polígono.
return false;
}
}
return true;
}
Para um polígono côncavo não poderíamos utilizar essa idéia. Porém poderíamos gerar os segmentos de reta que definem a borda do polígono, aplicando para cada par subseqüente o algoritmo do exercício 1. Por fim, varremos a matriz no sentido horizontal ou vertical. A cada “1” que encontrarmos, passamos a percorrer preenchendo as posições “0” com “1” até encontrar outra borda (encontrar uma posição que já contenha “1”). Assim, estaremos preenchendo o polígono inteiro (seja côncavo ou convexo).
4 – O algoritmo de back­face culling é semelhante ao algoritmo acima, porém em 3 dimensões. Iremos receber por entrada um conjunto de faces F = {F1, F2,..., Fn} e um ponto do observador, retornando um conjunto de faces visíveis. Para tanto, cada face Fi será definida por no mínimo 3 pontos consecutivos*. Digamos que esses pontos são P0(x0,y0,z0), *
É importante, nesses algoritmos, saber se a entrada de pontos, que é o conjunto que define o polígono ou face, será percorrida no sentido horário ou anti­horário para o desenho. Isto significa que, dados os pontos, devemos saber em que ordem eles foram nomeados (de P0 a Pn). Nos algoritmos usados, tomaremos por padrão que os pontos são nomeados no sentido horário. Isto significa que, percorrendo os segmentos no sentido horário, teremos primeiro P0, depois P1 e por fim, de Pn voltaremos a P1.
P1(x1,y1,z1) e P2(x2,y2,z2). Traçamos dois vetores arbitrários v (de P0 a P1) e w (de P0 a P2), por exemplo, onde v = P1 – P0 e w = P2 – P0. Iremos então, calcular o produto vetorial entre v e w, encontrando um vetor n que é o vetor normal da face em questão. Considerando v = (xv, yv, zv) e w = (xw, yw, zw), o produto vetorial n = (yv*zw – yw*zv, zv*xw – zw*xv, xv*yw – xw*yv). Traçaremos, depois, outro vetor, que irá de algum dos pontos da face (tomemos arbitrariamente o ponto P0.) até o ponto do observador (ponto Q). Teremos, então, outro vetor s = Q – P0. Agora, temos dois vetores, n e s, que são o suficiente para obtermos o resultado. Considerando n = (xn, yn, zn) e s = (xs, ys, zs). Calcularemos o produto escalar (valor escalar a) entre esses dois vetores, que é a = xn*xs + yn*ys + zn*zs. Se esse valor for menor que 0, o ângulo é maior que 90º e a face não será visível. Caso contrário, a face será visualizada e a face Fi é incluída na lista de faces na saída da função.
5 – Para isso, basta que a cada movimentação do observador se execute um teste antes de deslocar o observador para uma nova posição. Esse teste é também baseado nas normais dos planos. Iremos, portanto, usar parte do algoritmo do exercício 4. Digamos que o observador que se encontra em um ponto Qt se move para um ponto Qt+1. Usamos então, o ponto Qt+1 como entrada do algoritmo acima. Suponhamos que, para cada face Fi, tenhamos obtido os vetores s e n, e calculado o produto escalar a entre eles. Se esse produto escalar for menor ou igual a zero (a ≤ 0), o movimento do observador deve ser impedido e ele deve continuar na posição atual Qt. Caso contrário, a nova posição do observador passa a ser Qt+1. Para melhorar o algoritmo e impedir que o observador fique muito junto da face, o ideal seria estabelecer um threshold diferente de 0, isto é, o movimento será impedido quando a < 0,001, por exemplo. O valor ideal de threshold deve ser obtido com experimentação (e pode ser que se conclua que 0 é mesmo o valor ideal!).
6 – Para encontrarmos os vetores de base do Sistema de Referência da Câmera (SRC) precisaremos encontrar vetores calculando produtos vetoriais, que resultam em vetores ortogonais. Tomemos, então, um vetor que vai do ponto P (posicionamento da câmera) até o ponto A (alvo da câmera). Esse vetor v = A – P. Então, iremos calcular o produto vetorial w = v × vup e obteremos um vetor w que é ortogonal a v e a vup. Não temos nenhuma garantia de que v é ortogonal a vup. Portanto, iremos calcular um novo vetor s = vup × w. Esse vetor será ortogonal a vup e w, e vup é ortogonal a w. Portanto, temos que vup, w e s são três vetores ortogonais em ℜ 3, e uma vez que três vetores ortogonais são linearmente independentes, podemos construir uma base para o SRC, Β = {vup, w e s}.
Download