• Nenhum resultado encontrado

4 Alguns Algoritmos Clássicos

N/A
N/A
Protected

Academic year: 2021

Share "4 Alguns Algoritmos Clássicos"

Copied!
24
0
0

Texto

(1)

4 Alguns Algoritmos Clássicos

Embora a programação seja uma atividade muito criativa, quase sempre requerendo que

(pequenos) problemas inteiramente novos sejam resolvidos, às vezes podemos nos beneficiar de

algoritmos bem conhecidos, publicados por outros e que geralmente fornecem soluções mais eficientes ou

mais elegantes do que aquelas que teríamos sido capazes de inventar nós mesmos. Com a computação

gráfica isso não é diferente. Esse capítulo é sobre alguns algoritmos gráficos bem conhecidos para (a)

calcular as coordenadas dos pixels que constituem retas e círculos, (b) recortar retas e polígonos e (c)

desenhar curvas suaves. Essas são as operações mais primitivas em computação gráfica e devem ser

executadas com a maior rapidez possível. Portanto, os algoritmos neste capítulo são otimizados para evitar

execuções demoradas, como multiplicações, divisões e cálculos de ponto flutuante.

4.1 O Algoritmo de Bresenham para o Desenho de Retas

Discutiremos agora como desenhar retas colocando pixels na tela. Apesar de, em Java, podermos

simplesmente usar o método drawLine sem nos preocuparmos com pixels, isso seria insatisfatório se não

soubéssemos como esse método funciona. Infelizmente, Java não possui um método com o único

propósito de colocar um pixel na tela, de modo que definimos o seguinte método, um tanto estranho, para

obtermos isso:

void putPixel(Graphics g, int x, int y) { g.drawLine(g, x, y, x, y);

}

Agora desenvolvemos um método drawLine da forma

void drawLine(Graphics g, int xP, int yP, int xQ, int yQ) { ...

}

que só usa o método putPixel anterior para a saída gráfica.

A Figura 30 mostra um segmento de reta com pontos extremos P(1, 1) e Q(12, 5), assim como os

pixels que temos que calcular para aproximar essa reta.

Figura 30: Pontos de grade aproximando um segmento de reta

A primeira versão a seguir do método drawLine é baseada no arredondamento do pixel mais

próximo, sendo 0,5 a margem de erro aceitável:

void drawLine1(Graphics g, int xP, int yP, int xQ, int yQ) { int x = xP, y = yP;

float d = 0, m = (float)(yQ - yP)/(float)(xQ - xP); for (;;)

(2)

if (x == xQ) break; x++; d += m; if (d >= 0.5) {y++; d--;}; } }

A taxa de inclinação m e o erro d, presentes no código-fonte acima pode ser verificada na Figura 32, abaixo,

pois os pixels possuem endereçamento inteiro em vez de endereços baseados em números de ponto

flutuante (Figura 31).

Figura 31: Algoritmo incremental para definir a posição do próximo ponto

Figura 32: Inclinação m e erro d

Podemos melhorar o algoritmo do método drawLine trabalhando apenas com expressões inteiras:

void drawLine2(Graphics g, int xP, int yP, int xQ, int yQ) { int x = xP, y = yP, d = 0, dx = xQ – xP, c = 2 * dx, m = 2 * (yQ - yP); for (;;) { putPixel(g, x, y); if (x == xQ) break; x++; d += m; if (d >= dx) {y++; d -= c;}; } }

Para retas que possuem ângulo de inclinação superior a 45, existe a necessidade de se trocar os

papéis de x e y para evitar que os pixels selecionados fiquem muito longes. Estas situações estão incluídas

no método geral de desenho de retas drawLine a seguir:

(3)

void drawLine(Graphics g, int xP, int yP, int xQ, int yQ) { int x = xP, y = yP, d = 0, dx = xQ – xP, dy = yQ – yP, c, m, xInc = 1, yInc = 1;

if (dx < 0){xInc = -1; dx = -dx;} if (dy < 0){yInc = -1; dy = -dy;} if (dy <= dx) { c = 2 * dx; m = 2 * dy; if (xInc < 0) dx++; for (;;) { putPixel(g, x, y); if (x == xQ) break; x += xInc; d += m; if (d >= dx) {y += yInc; d -= c;}; } } else { c = 2 * dy; m = 2 * dx; if (yInc < 0) dy++; for (;;) { putPixel(g, x, y); if (y == yQ) break; y += yInc; d += m; if (d >= dy) {x += xInc; d -= c;}; } } }

A ideia de desenhar retas apenas usando variáveis inteiras foi primeiramente concebida por

Bresenham; seu nome é portanto associado a esse algoritmo.

4.2 Dobrando a Velocidade do Desenho de Retas

Como uma das operações gráficas básicas, o desenho de retas deve ser executado tão rapidamente

quanto possível. Na verdade, o hardware gráfico geralmente é avaliado pela velocidade na qual gera retas.

O algoritmo de retas de Bresenham é simples e eficiente na geração de retas, pois trabalha de forma

incremental calculando a posição do primeiro pixel a ser desenhado. Assim, ele itera tantas vezes quanto o

número de pixels a serem desenhados. O algoritmo de desenhos de retas em passo duplo de Rokne, Wyvill

e Wu (1990) objetiva a redução do número de iterações pela metade, calculando as posições dos próximos

dois pixels. Os quatro padrões de passo duplo possíveis são ilustrados na Figura 33:

Figura 33: Quatro padrões de passo duplo quando 0  inclinação  1

Os padrões 1 e 4 não podem ocorrer na mesma linha, como mostra a figura 34:

(4)

Como na seção anterior, ao discutirmos o algoritmo de Bresenham, começamos com um método

preliminar que ainda usa variáveis de ponto flutuante para torná-lo mais fácil de ser entendido, ainda que

não esteja otimizado tendo em vista a velocidade. Essa versão também funciona com retas desenhadas da

direita para a esquerda, ou seja, quando x

Q

< x

P

, assim como para retas com inclinação negativa. Todavia, o

valor absoluto da inclinação não deve ser maior que 1.

void doubleStep1(Graphics g, int xP, int yP, int xQ, int yQ) { int dx, dy, x, y, yInc;

if (xP >= xQ)

{ if (xP == xQ) // Não permitido porque dividimos por (dx = xQ - xP) return;

// xP > xQ, então permute os pontos P e Q int t;

t = xP; xP = xQ; xQ = t; t = yP; yP = yQ; yQ = t; }

// Agora xP < xQ

if (yQ >= yP){yInc = 1; dy = yQ - yP;} // Caso normal, yP < yQ else {yInc = -1; dy = yP - yQ;}

dx = xQ - xP; // dx > 0, dy > 0

float d = 0, // Erro d = yexact - y m = (float)dy/(float)dx; // m <= 1, m = |inclinação| putPixel(g, xP, yP); y = yP; for (x=xP; x<xQ-1;) { if (d + 2 * m < 0.5) // Padrão 1: { putPixel(g, ++x, y); putPixel(g, ++x, y);

d += 2 * m; // O erro aumenta em 2m, já que y permanece // inalterado e yexact aumenta em 2m

} else if (d + 2 * m < 1.5) // Padrão 2 ou 3 { if (d + m < 0.5) // Padrão 2 { putPixel(g, ++x, y); putPixel(g, ++x, y += yInc);

d += 2 * m - 1; // Devido a ++y, o erro é agora // 1 a menos que com padrão 1 } else // Padrão 3 { putPixel(g, ++x, y += yInc); putPixel(g, ++x, y); d += 2 * m - 1; // Mesmo do padrão 2 } } else // Padrão 4: { putPixel(g, ++x, y += yInc); putPixel(g, ++x, y += yInc);

d += 2 * m - 2; // Devido a y += 2, o erro é agora // 2 a menos que com o padrão 1 }

}

if (x < xQ) // x = xQ - 1 putPixel(g, xQ, yQ); }

Uma versão mais eficiente da implementação para desenhar retas com padrões de passo duplo,

utilizando números inteiros em vez de ponto flutuante é apresentado a seguir, no método doubleStep2:

void doubleStep2(Graphics g, int xP, int yP, int xQ, int yQ) { int dx, dy, x, y, yInc;

(5)

if (xP >= xQ)

{ if (xP == xQ) // Não permitido porque dividimos por (dx = xQ - xP) return;

int t; // xP > xQ, então permute os pontos P e Q t = xP; xP = xQ; xQ = t;

t = yP; yP = yQ; yQ = t; }

// Agora xP < xQ

if (yQ >= yP){yInc = 1; dy = yQ - yP;} else {yInc = -1; dy = yP - yQ;} dx = xQ - xP;

int dy4 = dy * 4, v = dy4 - dx, dx2 = 2 * dx, dy2 = 2 * dy, dy4Minusdx2 = dy4 - dx2, dy4Minusdx4 = dy4Minusdx2 - dx2; putPixel(g, xP, yP);

y = yP;

for (x=xP; x<xQ-1;)

{ if (v < 0) // Equivalente a d + 2 * m < 0.5 { putPixel(g, ++x, y); // Padrão 1

putPixel(g, ++x, y); v += dy4; // Equivalente a d += 2 * m } else if (v < dx2) // Equivalente a d + 2 * m < 1.5 { // Padrão 2 ou 3 if (v < dy2) // Equivalente a d + m < 0.5 { putPixel(g, ++x, y); // Padrão 2

putPixel(g, ++x, y += yInc);

v += dy4Minusdx2; // Equivalente a d += 2 * m - 1 }

else

{ putPixel(g, ++x, y += yInc); // Padrão 3 putPixel(g, ++x, y);

v += dy4Minusdx2; // Equivalente a d += 2 * m - 1 }

} else

{ putPixel(g, ++x, y += yInc); // Padrão 4 putPixel(g, ++x, y += yInc); v += dy4Minusdx4; // Equivalente a d += 2 * m - 2 } } if (x < xQ) putPixel(g, xQ, yQ); }

O método doubleStep2 acima só funciona se

. Se desejar, o método acima

pode ser generalizado para trabalhar com qualquer reta, como foi feito com o algoritmo de Bresenham.

Da mesma forma que o algoritmo de Bresenham, o algoritmo passo duplo calcula apenas com

inteiros. Para retas longas ele tem um desempenho quase duas vezes melhor que o algoritmo de

Bresenham. Pode-se otimizá-lo ainda mais para se obter uma outra duplicação de velocidade

aproveitando-se da simetria em torno do ponto central de uma determinada reta.

4.3 Círculos

Nesta seção iremos ignorar a forma usual de se desenhar um círculo em Java por meio de

chamadas como

(6)

já que é nosso objetivo construirmos nós mesmos tal círculo, com centro C(x

C

, y

C

) e raio r, em que as

coordenadas de C e do raio são dadas como inteiros. Desenvolveremos um método na forma de:

void drawCircle(Graphics g, int xC, int yC, int r) { ...

}

que só usa o método putPixel da seção anterior como “primitiva gráfica” e que é uma implementação do

algoritmo de Bresenham para círculos. O círculo desenhado dessa forma será exatamente o mesmo

produzido pela chamada anterior drawOval. Em ambos os casos, x varia de xC – r a xC + r, incluindo esses

dois valores, de modo que 2r + 1 valores de r serão usados.

Assim como nas seções anteriores, começamos com um caso simples: usamos a origem do sistema

de coordenadas como o centro do círculo, e, dividindo o círculo em oito arcos de comprimento igual, nos

restringimos a um deles, o arco PQ. A equação deste círculo é:

x

2

+ y

2

= r

2

A Figura 35 mostra a situação, incluindo a grade de pixels, para o caso de r = 8. Começando pelo

topo no ponto P, com x = 0 e y = r, usaremos um laço no qual incrementamos x em 1 a cada passo; como na

seção anterior, precisamos de um teste para decidir se podemos deixar y sem alteração. Se esse não for o

caso, temos que decrementar y em 1.

Figura 35: Pixels que aproximam o arco PQ

Para tornar nosso algoritmo mais rápido, evitaremos calcular os quadrados x

2

e y

2

, introduzindo

três novas variáveis inteiras não negativas u, v e E denotando as diferenças entre dois quadrados sucessivos

e o ‘erro’:

= ( − 1) −

= 2 + 1

=

− ( − 1) = 2 + 1

=

+

Agora podemos escrever o seguinte método para desenhar o arco PQ:

void arc8(Graphics g, int r)

{ int x = 0, y = r, u = 1, v = 2 * r - 1, e = 0; while (x <= y) { putPixel(g, x, y); x++; e += u; u += 2; if (v < 2 * e) {y--; e -= v; v -= 2;} } }

(7)

O método arc8 é a base do nosso método final, drawCircle, listado a seguir. Além de desenhar um

círculo completo, ele também é mais geral do que arc8 por permitir que um ponto C arbitrário seja

especificado como o centro do círculo.

void drawCircle(Graphics g, int xC, int yC, int r) { int x = 0, y = r, u = 1, v = 2 * r - 1, E = 0; while (x < y)

{ putPixel(g, xC + x, yC + y); // NNE putPixel(g, xC + y, yC - x); // ESE putPixel(g, xC - x, yC - y); // SSW putPixel(g, xC - y, yC + x); // WNW x++; E += u; u += 2; if (v < 2 * E){y--; E -= v; v -= 2;} if (x > y) break; putPixel(g, xC + y, yC + x); // ENE putPixel(g, xC + x, yC - y); // SSE putPixel(g, xC - y, yC - x); // WSW putPixel(g, xC - x, yC + y); // NNW }

}

4.4 Recorte de Retas Cohen-Sutherland

Nesta seção discutiremos como desenhar segmentos de retas apenas quando estiverem dentro de

um determinado retângulo. As decisões lógicas necessárias para se descobrir que ações tomar tornam o

recorte de retas um tópico interessante do ponto de vista algorítmico. O algoritmo Cohen-Sutherland

resolve esse problema de uma forma elegante e eficiente. Expressaremos esse algoritmo em Java (classe

ClipLine):

import java.awt.*;

import java.awt.event.*; import java.util.*;

public class ClipLine extends Frame

{ public static void main(String[] args){new ClipLine();}

ClipLine()

{ super("Clique em dois vértices opostos de um retângulo"); addWindowListener(new WindowAdapter()

{public void windowClosing(WindowEvent e){System.exit(0);}}); setSize(500, 300);

add("Center", new CvClipLine());

setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); show();

} }

class CvClipLine extends Canvas { float xmin, xmax, ymin, ymax,

rWidth = 10.0F, rHeight = 7.5F, pixelSize; int maxX, maxY, centerX, centerY, np=0;

CvClipLine()

{ addMouseListener(new MouseAdapter()

{ public void mousePressed(MouseEvent evt)

{ float x = fx(evt.getX()), y = fy(evt.getY()); if (np == 2) np = 0;

if (np == 0){xmin = x; ymin = y;} else

{ xmax = x; ymax = y; if (xmax < xmin)

(8)

{ float t = xmax; xmax = xmin; xmin = t; }

if (ymax < ymin)

{ float t = ymax; ymax = ymin; ymin = t; } } np++; repaint(); } }); } void initgr() { Dimension d = getSize();

maxX = d.width - 1; maxY = d.height - 1;

pixelSize = Math.max(rWidth/maxX, rHeight/maxY); centerX = maxX/2; centerY = maxY/2;

}

int iX(float x){return Math.round(centerX + x/pixelSize);} int iY(float y){return Math.round(centerY - y/pixelSize);} float fx(int x){return (x - centerX) * pixelSize;}

float fy(int y){return (centerY - y) * pixelSize;}

void drawLine(Graphics g, float xP, float yP, float xQ, float yQ)

{ g.drawLine(iX(xP), iY(yP), iX(xQ), iY(yQ)); }

int clipCode(float x, float y) { return

(x < xmin ? 8 : 0) | (x > xmax ? 4 : 0) | (y < ymin ? 2 : 0) | (y > ymax ? 1 : 0); }

void clipLine(Graphics g,

float xP, float yP, float xQ, float yQ,

float xmin, float ymin, float xmax, float ymax) { int cP = clipCode(xP, yP), cQ = clipCode(xQ, yQ); float dx, dy;

while ((cP | cQ) != 0)

{ if ((cP & cQ) != 0) return; dx = xQ - xP; dy = yQ - yP; if (cP != 0)

{ if ((cP & 8) == 8){yP += (xmin-xP) * dy / dx; xP = xmin;} else

if ((cP & 4) == 4){yP += (xmax-xP) * dy / dx; xP = xmax;} else

if ((cP & 2) == 2){xP += (ymin-yP) * dx / dy; yP = ymin;} else

if ((cP & 1) == 1){xP += (ymax-yP) * dx / dy; yP = ymax;} cP = clipCode(xP, yP);

} else

if (cQ != 0)

{ if ((cQ & 8) == 8){yQ += (xmin-xQ) * dy / dx; xQ = xmin;} else

if ((cQ & 4) == 4){yQ += (xmax-xQ) * dy / dx; xQ = xmax;} else

if ((cQ & 2) == 2){xQ += (ymin-yQ) * dx / dy; yQ = ymin;} else

if ((cQ & 1) == 1){xQ += (ymax-yQ) * dx / dy; yQ = ymax;} cQ = clipCode(xQ, yQ);

(9)

}

drawLine(g, xP, yP, xQ, yQ); }

public void paint(Graphics g) { initgr();

if (np == 1)

{ // Desenha linhas horizontais e verticais através // do primeiro ponto definido:

drawLine(g, fx(0), ymin, fx(maxX), ymin); drawLine(g, xmin, fy(0), xmin, fy(maxY)); } else

if (np == 2)

{ // Desenha retângulo:

drawLine(g, xmin, ymin, xmax, ymin); drawLine(g, xmax, ymin, xmax, ymax); drawLine(g, xmax, ymax, xmin, ymax); drawLine(g, xmin, ymax, xmin, ymin);

// Desenha 20 pentágonos regulares concêntricos,

// desde que eles estejam localizados dentro do retângulo: float rMax = Math.min(rWidth, rHeight)/2,

deltaR = rMax/20, dPhi = (float)(0.4 * Math.PI);

for (int j=1; j<=20; j++) { float r = j * deltaR; // Desenha um pentágono: float xA, yA, xB = r, yB = 0;

for (int i=1; i<=5; i++) { float phi = i * dPhi; xA = xB; yA = yB;

xB = (float)(r * Math.cos(phi)); yB = (float)(r * Math.sin(phi));

clipLine(g, xA, yA, xB, yB, xmin, ymin, xmax, ymax); }

} } } }

O programa desenha 20 pentágonos concêntricos (regulares), desde que se localizem dentro de um

retângulo, que o usuário pode definir clicando em quaisquer dois vértices opostos. Quando ele clica pela

terceira vez, a situação é a mesma do início: a tela é limpa e um novo retângulo pode ser definido, no qual

novamente partes de 20 pentágonos aparecem, e assim por diante. Como de costume, se o usuário alterar

as dimensões da janela, o tamanho do desenho é alterado apropriadamente. A Figura 36 mostra a situação

logo após os pentágonos terem sido desenhados.

(10)

4.5 Recorde de Polígonos de Sutherland-Hodgman

Em contraste com o recorte de retas, discutido na seção anterior, agora lidaremos com o recorte de

polígonos, que é diferente pelo fato de converter um polígono em outro dentro de um determinado

retângulo, como as Figuras 37 e 38 ilustram.

Figura 37: Nove vértices do polígono definidos; o lado final ainda não está desenhado

Figura 38: Polígono completo e recortado

O programa que discutiremos desenha um retângulo fixo e permite ao usuário especificar os

vértices de um polígono clicando na mesma forma discutida na seção 1.5. Desde que o primeiro vértice, na

Figura 37 acima, não seja selecionado pela segunda vez, sucessivos vértices são conectados pelos lados do

polígono. Assim que o primeiro vértice é selecionado novamente, o polígono é recortado, como a Figura 38

mostra. Alguns vértices do polígono original não pertencem ao polígono recortado. Por outro lado, este

último polígono possui alguns vértices novos, que são todos pontos de interseção dos lados do polígono

original com os do retângulo. De modo geral, o número de vértices do polígono recortado pode ser maior,

igual ou menor que o do original. Na Figura 38 há cinco novos lados do polígono, que são parte dos lados

do retângulo.

(11)

O programa que produziu a Figura 38 é baseado no algoritmo de Sutherland-Hodgman, que

primeiro recorta todos os lados do polígono contra um lado do retângulo, ou melhor, a reta infinita através

de tal lado. Isso resulta em um novo polígono, que é então recortado contra o próximo lado do retângulo, e

assim por diante. A Figura 39 ilustra este processo. A Figura 40 mostra um retângulo e um polígono,

ABCDEF, gerando um novo polígono de saída IJKLFA.

Figura 39: Recorta o polígono dado contra um lado do retângulo por vez

Figura 40: Polígono ABCDEF recortado para IJKLFA

O programa a seguir (ClipPoly.java) mostra uma implementação em Java para executar o corte de

polígonos fora da área de desenho.

import java.awt.*;

import java.awt.event.*; import java.util.*;

public class ClipPoly extends Frame

{ public static void main(String[] args){new ClipPoly();} ClipPoly()

{ super("Defina os vértices do polígono clicando"); addWindowListener(new WindowAdapter()

{public void windowClosing(WindowEvent e){System.exit(0);}}); setSize(500, 300);

add("Center", new CvClipPoly());

setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); show();

} }

(12)

class CvClipPoly extends Canvas { Poly poly = null;

float rWidth = 10.0F, rHeight = 7.5F, pixelSize; int x0, y0, centerX, centerY;

boolean ready = true;

CvClipPoly()

{ addMouseListener(new MouseAdapter()

{ public void mousePressed(MouseEvent evt) { int x = evt.getX(), y = evt.getY(); if (ready)

{ poly = new Poly(); x0 = x; y0 = y; ready = false; }

if (poly.size() > 0 &&

Math.abs(x - x0) < 3 && Math.abs(y - y0) < 3) ready = true;

else

poly.addVertex(new Point2D(fx(x), fy(y))); repaint(); } }); } void initgr() { Dimension d = getSize();

int maxX = d.width - 1, maxY = d.height - 1; pixelSize = Math.max(rWidth/maxX, rHeight/maxY); centerX = maxX/2; centerY = maxY/2;

}

int iX(float x){return Math.round(centerX + x/pixelSize);} int iY(float y){return Math.round(centerY - y/pixelSize);} float fx(int x){return (x - centerX) * pixelSize;}

float fy(int y){return (centerY - y) * pixelSize;}

void drawLine(Graphics g, float xP, float yP, float xQ, float yQ) { g.drawLine(iX(xP), iY(yP), iX(xQ), iY(yQ));

}

void drawPoly(Graphics g, Poly poly) { int n = poly.size();

if (n == 0) return;

Point2D a = poly.vertexAt(n - 1); for (int i=0; i<n; i++)

{ Point2D b = poly.vertexAt(i); drawLine(g, a.x, a.y, b.x, b.y); a = b;

} }

public void paint(Graphics g) { initgr();

float xmin = -rWidth/3, xmax = rWidth/3, ymin = -rHeight/3, ymax = rHeight/3; // Desenha o retângulo de corte:

g.setColor(Color.blue);

drawLine(g, xmin, ymin, xmax, ymin); drawLine(g, xmax, ymin, xmax, ymax); drawLine(g, xmax, ymax, xmin, ymax); drawLine(g, xmin, ymax, xmin, ymin); g.setColor(Color.black);

(13)

int n = poly.size(); if (n == 0) return;

Point2D a = poly.vertexAt(0); if (!ready)

{ // Mostra um pequeno retângulo em torno do primeiro vértice: g.drawRect(iX(a.x)-2, iY(a.y)-2, 4, 4);

// Desenha polígono incompleto: for (int i=1; i<n; i++)

{ Point2D b = poly.vertexAt(i); drawLine(g, a.x, a.y, b.x, b.y); a = b;

} } else

{ poly.clip(xmin, ymin, xmax, ymax); drawPoly(g, poly);

} } }

class Poly

{ Vector v = new Vector();

void addVertex(Point2D p){v.addElement(p);} int size(){return v.size();}

Point2D vertexAt(int i)

{ return (Point2D)v.elementAt(i); }

void clip(float xmin, float ymin, float xmax, float ymax) { // Recorte de polígono de Sutherland-Hodgman:

Poly poly1 = new Poly(); int n;

Point2D a, b;

boolean aIns, bIns; // se A ou B estiver no mesmo // lado que o retângulo

// Corte contra x == xmax: if ((n = size()) == 0) return; b = vertexAt(n-1);

for (int i=0; i<n; i++) { a = b; b = vertexAt(i);

aIns = a.x <= xmax; bIns = b.x <= xmax; if (aIns != bIns)

poly1.addVertex(new Point2D(xmax, a.y + (b.y - a.y) * (xmax - a.x)/(b.x - a.x))); if (bIns) poly1.addVertex(b);

}

v = poly1.v; poly1 = new Poly();

// Corte contra x == xmin: if ((n = size()) == 0) return; b = vertexAt(n-1);

for (int i=0; i<n; i++) { a = b; b = vertexAt(i);

aIns = a.x >= xmin; bIns = b.x >= xmin; if (aIns != bIns)

poly1.addVertex(new Point2D(xmin, a.y + (b.y - a.y) * (xmin - a.x)/(b.x - a.x))); if (bIns) poly1.addVertex(b);

}

v = poly1.v; poly1 = new Poly();

// Corte contra y == ymax: if ((n = size()) == 0) return; b = vertexAt(n-1);

(14)

for (int i=0; i<n; i++) { a = b; b = vertexAt(i);

aIns = a.y <= ymax; bIns = b.y <= ymax; if (aIns != bIns)

poly1.addVertex(new Point2D(a.x +

(b.x - a.x) * (ymax - a.y)/(b.y - a.y), ymax)); if (bIns) poly1.addVertex(b);

}

v = poly1.v; poly1 = new Poly();

// Corte contra y == ymin: if ((n = size()) == 0) return; b = vertexAt(n-1);

for (int i=0; i<n; i++) { a = b; b = vertexAt(i); aIns = a.y >= ymin; bIns = b.y >= ymin; if (aIns != bIns)

poly1.addVertex(new Point2D(a.x +

(b.x - a.x) * (ymin - a.y)/(b.y - a.y), ymin)); if (bIns) poly1.addVertex(b);

}

v = poly1.v; poly1 = new Poly(); }

}

O algoritmo de Sutherland-Hodgman pode ser adaptado para o recorte de outras áreas que não

retângulos e para aplicações tridimensionais.

4.6 Curvas de Bézier

Há muitos algoritmos para desenhar curvas. Um especialmente elegante e prático é baseado na

especificação de quatro pontos que determinam totalmente um segmento de curva: dois pontos extremos

e dois pontos de controle. Curvas desenhadas dessa forma são chamadas de curvas (cúbicas) de Bézier. Na

Figura 41, temos os pontos extremos P

0

e P

3

, os pontos de controle P

1

e P

2

, e a curva desenhada com base

nesses quatro pontos.

Figura 41: Curva de Bézier baseada em quatro pontos

Escrever um método para desenhar essa curva é surpreendentemente fácil, desde que usemos

recursão. Como mostra a Figura 42, calculamos seis pontos médios, a saber:

A, o ponto médio de P

0

P

1

B, o ponto médio de P

2

P

3

C, o ponto médio de P

1

P

2

(15)

B

1

, o ponto médio de BC

C

1

, o ponto médio de A

1

B

1

Figura 42: Definindo os pontos para dois segmentos de curva menores

Após isso, podemos dividir a tarefa original de desenhar a curva de Bézier P

0

P

3

(com pontos de

controle P

1

e P

2

) em duas tarefas mais simples:

Desenhar a curva de Bézier P

0

C

1

, com pontos de controle A e A

1

Desenhar a curva de Bézier C

1

P

3

, com pontos de controle B

1

e B

O método recursivo bezier no programa a seguir mostra uma implementação desse algoritmo. O

programa espera que o usuário especifique quatro pontos P

0

, P

1

, P

2

e P

3

, nessa ordem, clicando com o

mouse. Após o quarto ponto, P

3

, ter sido especificado, a curva é desenhada. Qualquer novo clique do

mouse é interpretado como o primeiro ponto P

0

de uma nova curva; a curva anterior simplesmente

desaparece e outra curva pode ser construída da mesma maneira que a primeira, e assim por diante.

import java.awt.*;

import java.awt.event.*; import java.util.*;

public class Bezier extends Frame

{ public static void main(String[] args){new Bezier();}

Bezier()

{ super("Defina os pontos extremos e de controle do segmento"); addWindowListener(new WindowAdapter()

{public void windowClosing(WindowEvent e){System.exit(0);}}); setSize(500, 300);

add("Center", new CvBezier());

setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); show();

} }

class CvBezier extends Canvas { Point2D[] p = new Point2D[4]; int np = 0, centerX, centerY;

float rWidth = 10.0F, rHeight = 7.5F, eps = rWidth/100F, pixelSize;

CvBezier()

{ addMouseListener(new MouseAdapter()

{ public void mousePressed(MouseEvent evt)

{ float x = fx(evt.getX()), y = fy(evt.getY()); if (np == 4) np = 0;

p[np++] = new Point2D(x, y); repaint();

(16)

}); }

void initgr()

{ Dimension d = getSize();

int maxX = d.width - 1, maxY = d.height - 1; pixelSize = Math.max(rWidth/maxX, rHeight/maxY); centerX = maxX/2; centerY = maxY/2;

}

int iX(float x){return Math.round(centerX + x/pixelSize);} int iY(float y){return Math.round(centerY - y/pixelSize);} float fx(int x){return (x - centerX) * pixelSize;}

float fy(int y){return (centerY - y) * pixelSize;} Point2D middle(Point2D a, Point2D b)

{ return new Point2D((a.x + b.x)/2, (a.y + b.y)/2); }

void bezier(Graphics g, Point2D p0, Point2D p1, Point2D p2, Point2D p3)

{ int x0 = iX(p0.x), y0 = iY(p0.y), x3 = iX(p3.x), y3 = iY(p3.y);

if (Math.abs(x0 - x3) <= 1 && Math.abs(y0 - y3) <= 1) g.drawLine(x0, y0, x3, y3);

else

{ Point2D a = middle(p0, p1), b = middle(p3, p2), c = middle(p1, p2), a1 = middle(a, c), b1 = middle(b, c), c1 = middle(a1, b1); bezier(g, p0, a, a1, c1); bezier(g, c1, b1, b, p3); } }

public void paint(Graphics g) { initgr();

int left = iX(-rWidth/2), right = iX(rWidth/2), bottom = iY(-rHeight/2), top = iY(rHeight/2); g.drawRect(left, top, right - left, bottom - top);

for (int i=0; i<np; i++)

{ // Mostra pequeno retângulo em torno do ponto: g.drawRect(iX(p[i].x)-2, iY(p[i].y)-2, 4, 4); if (i > 0)

// Desenha reta p[i-1]p[i]:

g.drawLine(iX(p[i-1].x), iY(p[i-1].y), iX(p[i].x), iY(p[i].y)); } if (np == 4) bezier(g, p[0], p[1], p[2], p[3]); } }

Como esse programa usa o modo de mapeamento isotrópico com intervalo de coordenadas lógicas

0-10,0 para x e 0-7,5 para y, só devemos usar um retângulo cuja altura seja 75% de sua largura. Como nas

seções 1.4 e 1.5, colocamos esse retângulo no centro da tela e o tornamos o maior possível. Ele é mostrado

na Figura 43; se os quatro pontos para a curva forem escolhidos dentro desse retângulo, eles serão visíveis

independentemente de como o tamanho da janela é alterado pelo usuário. O mesmo se aplica à curva, que

é automaticamente escalada, da mesma forma que fizemos nas seções 1.4 e 1.5.

(17)

Figura 43: Uma curva de Bézier desenhada

Como métodos recursivos podem gastar muitos recursos computacionais, podemos substituir o

método recursivo bezier, do programa apresentado acima, pelo seguinte método não-recursivo:

void bezier1(Graphics g, Point2D[] p) { int n = 200;

float dt = 1.0F/n, x = p[0].x, y = p[0].y, x0, y0; for (int i=1; i<=n; i++)

{ float t = i * dt, u = 1 - t, tuTriple = 3 * t * u, c0 = u * u * u, c1 = tuTriple * u, c2 = tuTriple * t, c3 = t * t * t; x0 = x; y0 = y; x = c0*p[0].x + c1*p[1].x + c2*p[2].x + c3*p[3].x; y = c0*p[0].y + c1*p[1].y + c2*p[2].y + c3*p[3].y; g.drawLine(iX(x0), iY(y0), iX(x), iY(y));

} }

Este método produz a mesma curva que a produzida por bezier, desde que também substituamos a

chamada a bezier por esta:

bezier1(g, p);

Considerando que B(t) denota a posição no tempo t, a derivada B’(t) dessa função (que também é

um vetor coluna em t) pode ser considerada a velocidade. Após alguma manipulação algébrica, a derivação

resulta em:

B’(t) = -3(t - 1)

2

P

0

+ 3(3t - 1)(t - 1)P

1

– 3t(3t - 2)(t -1)P

2

+ 3t

2

P

3

o que dá:

B’(0) = 3(P

1

– P

0

)

B’(1) = 3(P

3

– P

2

)

Esses dois resultados são os vetores de velocidade no ponto inicial P

0

e no ponto final P

3

. Eles mostram que

(18)

Figura 44: Velocidade nos pontos P0 e P3

Discutimos duas formas inteiramente diferentes de desenhar uma curva entre os pontos P

0

e P

3

, e

sem um experimento não fica claro que essas curvas são idênticas. Por enquanto, faremos distinção entre

as duas curvas e as chamaremos de:

Curva do ponto médio: desenhada por um processo recursivo de cálculo dos pontos médios e

implementada no método bezier;

Curva analítica: Dara pela equação considerando o tempo, calculando a velocidade dos vetores,

implementada no método bezier1.

4.6.1 Desenhando Curvas Suaves a Partir de Segmentos de Curvas

Suponha que queiramos combinar dois segmentos de curvas de Bézier, um baseado nos quatro

pontos P

0

, P

1

, P

2

e P

3

e o outro nos pontos Q

0

, Q

1

, Q

2

e Q

3

, de forma que o ponto final P

3

do primeiro

segmento coincida com o ponto inicial Q

0

do segundo. A curva resultante será mais suave se a velocidade

final B’(1) (veja a Figura 44) do primeiro segmento for igual à velocidade inicial B’(0) do segundo. Esse será

o caso se o ponto P

3

(=Q

0

) estiver exatamente no meio do segmento de reta P

2

Q

1

. O alto grau de suavidade

obtido dessa forma é chamado de continuidade de segunda ordem. Isso implica não apenas que os dois

segmentos possuem a mesma tangente no seu ponto comum P

3

= Q

0

, mas também que a curvatura é

contínua nesse ponto. Em contraste, temos continuidade de primeira ordem se P

3

se localizar no segmento

de reta P

2

Q

1

mas não no meio dele. Nesse caso, embora a curva pareça razoavelmente suave porque

ambos os segmentos possuem a mesma tangente no ponto comum P

3

= Q

0

, há uma descontinuidade na

curvatura nesse ponto. A Figura 45 ilustra esta a suavização de curvas discutidas aqui.

Figura 45: Curvas suaves através de segmentos de curva

4.6.2 Notação Matricial

A equação apresentada e discutida anteriormente pode ser reduzida para:

( ) = (−

+ 3

+ 3

+

)

+ 3(

− 2

+

)

− 3(

) +

Isso é interessante porque nos fornece uma forma muito eficiente de desenhar um segmento de

curva de Bézier, como nos mostra o seguinte método melhorado:

(19)

void bezier2(Graphics g, Point2D[] p) { int n = 200;

float dt = 1.0F/n,

cx3 = -p[0].x + 3 * (p[1].x - p[2].x) + p[3].x, cy3 = -p[0].y + 3 * (p[1].y - p[2].y) + p[3].y, cx2 = 3 * (p[0].x - 2 * p[1].x + p[2].x),

cy2 = 3 * (p[0].y - 2 * p[1].y + p[2].y), cx1 = 3 * (p[1].x - p[0].x),

cy1 = 3 * (p[1].y - p[0].y), cx0 = p[0].x, cy0 = p[0].y, x = p[0].x, y = p[0].y, x0, y0; for (int i=1; i<=n; i++)

{ float t = i * dt; x0 = x; y0 = y;

x = ((cx3 * t + cx2) * t + cx1) * t + cx0; y = ((cy3 * t + cy2) * t + cy1) * t + cy0; g.drawLine(iX(x0), iY(y0), iX(x), iY(y)); }

}

Embora bezier2 não pareça mais simples que bezier1, é muito mais eficiente devido ao número

reduzido de operações aritméticas no laço for. Com um grande número de passos, como n = 200 nessas

versões de bezier1 e bezier2, é o número de operações dentro do laço que conta, e não as ações

preparatórias que precedem o laço.

4.6.3 Curvas 3D

Embora as curvas discutidas aqui sejam bidimensionais, curvas tridimensionais podem ser geradas

da mesma forma. Simplesmente adicionamos um componente z a B(t) e aos pontos de controle, que será

calculado da mesma forma que os componentes x e y.

4.7 Ajuste de Curvas B-Spline

Além das técnicas discutidas na seção anterior, há outras formas de gerar curvas x = f(t) e y = g(t),

em que f e g são polinômios de terceiro grau em t. Uma técnica popular, conhecida como B-Splines, possui

a característica de que a curva gerada normalmente não passa pelos pontos dados. Chamaremos todos

esses pontos de pontos de controle. Um único segmento de tal curva, baseado em quatro pontos de

controle A, B, C e D, parece bastante desapontador pois dá a impressão de estar relacionado apenas a B e

C. Isso é mostrado na Figura 46, na qual, da esquerda para a direita, os pontos A, B, C e D estão marcados

novamente com pequenos quadrados.

(20)

Todavia, um ponto forte a favor de B-Splines é que essa técnica facilita o desenho de curvas muito

suaves que consistem em muitos segmentos de curva. Como você pode ver na Figura 47, a curva é de fato

bastante suave: temos continuidade de segunda ordem, conforme discutido na seção anterior. Lembre-se

de que isso implica que até a curvatura é contínua nos pontos em que dois segmentos de curva adjacentes

se encontram. Como mostra a parte da curva próxima do vértice inferior direito, podemos tornar a

distância entre uma curva e os pontos dados muito pequena ao fornecer diversos pontos próximos uns dos

outros. A Figura 47 abaixo teve o seu primeiro ponto de controle iniciado na parte inferior esquerda,

seguindo para a parte superior esquerda, continuando nos pontos seguintes, no sentido horário, voltando

ao primeiro e segundo pontos marcados novamente (note que o segmento de reta à esquerda parece estar

mais grossa).

Figura 47: Curva B-Spline consistindo em cinco segmentos de curva

A equação abaixo serve para definir as curvas B-Spline:

( ) = (−

+ 3

− 3

+

)

+ (

− 2

+

)

+ (−

+

) + (

+ 4

+

)

O programa a seguir se baseia nessa equação. O usuário pode clicar qualquer número de pontos,

que são usados como os pontos P

0

, P

1

, ..., P

n-1

. O primeiro segmento de curva aparece imediatamente após

o quarto ponto de controle, P

3

, ter sido definido, e cada ponto de controle adicional faz com que um novo

segmento de curva apareça. Para mostrar apenas a curva, o usuário pode pressionar qualquer tecla, o que

também termina o processo de entrada. Após isso, podemos gerar outra curva clicando o mouse

novamente. As Figuras 46 e 47 foram produzidas por este programa:

import java.awt.*;

import java.awt.event.*; import java.util.*;

public class Bspline extends Frame

{ public static void main(String[] args){new Bspline();}

Bspline()

{ super("Defina pontos: pressione qualquer tecla após o final"); addWindowListener(new WindowAdapter()

{public void windowClosing(WindowEvent e){System.exit(0);}}); setSize(500, 300);

(21)

setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); show();

} }

class CvBspline extends Canvas { Vector V = new Vector(); int np = 0, centerX, centerY;

float rWidth = 10.0F, rHeight = 7.5F, eps = rWidth/100F, pixelSize; boolean ready = false;

CvBspline()

{ addMouseListener(new MouseAdapter()

{ public void mousePressed(MouseEvent evt)

{ float x = fx(evt.getX()), y = fy(evt.getY()); if (ready)

{ V.removeAllElements(); np = 0;

ready = false; }

V.addElement(new Point2D(x, y)); np++;

repaint(); }

});

addKeyListener(new KeyAdapter()

{ public void keyTyped(KeyEvent evt) { evt.getKeyChar(); if (np >= 4) ready = true; repaint(); } }); } void initgr() { Dimension d = getSize();

int maxX = d.width - 1, maxY = d.height - 1; pixelSize = Math.max(rWidth/maxX, rHeight/maxY); centerX = maxX/2; centerY = maxY/2;

}

int iX(float x){return Math.round(centerX + x/pixelSize);} int iY(float y){return Math.round(centerY - y/pixelSize);} float fx(int x){return (x - centerX) * pixelSize;}

float fy(int y){return (centerY - y) * pixelSize;}

void bspline(Graphics g, Point2D[] p) { int m = 50, n = p.length;

float xA, yA, xB, yB, xC, yC, xD, yD,

a0, a1, a2, a3, b0, b1, b2, b3, x=0, y=0, x0, y0; boolean first = true;

for (int i=1; i<n-2; i++)

{ xA=p[i-1].x; xB=p[i].x; xC=p[i+1].x; xD=p[i+2].x; yA=p[i-1].y; yB=p[i].y; yC=p[i+1].y; yD=p[i+2].y; a3=(-xA+3*(xB-xC)+xD)/6; b3=(-yA+3*(yB-yC)+yD)/6; a2=(xA-2*xB+xC)/2; b2=(yA-2*yB+yC)/2; a1=(xC-xA)/2; b1=(yC-yA)/2; a0=(xA+4*xB+xC)/6; b0=(yA+4*yB+yC)/6; for (int j=0; j<=m; j++) { x0 = x; y0 = y; float t = (float)j/(float)m; x = ((a3*t+a2)*t+a1)*t+a0; y = ((b3*t+b2)*t+b1)*t+b0;

(22)

if (first) first = false; else

g.drawLine(iX(x0), iY(y0), iX(x), iY(y)); }

} }

public void paint(Graphics g) { initgr();

int left = iX(-rWidth/2), right = iX(rWidth/2), bottom = iY(-rHeight/2), top = iY(rHeight/2); g.drawRect(left, top, right - left, bottom - top); Point2D[] p = new Point2D[np];

V.copyInto(p); if (!ready)

{ for (int i=0; i<np; i++)

{ // Mostra pequeno retângulo em torno do ponto: g.drawRect(iX(p[i].x)-2, iY(p[i].y)-2, 4, 4); if (i > 0)

// Desenha reta p[i-1]p[i]:

g.drawLine(iX(p[i-1].x), iY(p[i-1].y), iX(p[i].x), iY(p[i].y)); } } if (np >= 4) bspline(g, p); } }

Para ver porque B-Splines são tão suaves, você deve derivar B(t) duas vezes e verificar que, para

qualquer segmento que não seja o final, os valores de B(1), B’(1) e B’’(1) no ponto final desses segmentos

são iguais aos valores B(0), B’(0) e B’’(0) no ponto inicial do próximo segmento de curva. Por exemplo, para

a continuidade da própria curva, encontramos

(1) = (−

+ 3

− 3

+

)

+ (

− 2

+

)

+ (−

+

) + (

+ 4

+

)

= (

+ 4

+

)

para o primeiro segmento, baseado em P

0

, P

1

, P

2

e P

3

, enquanto podemos ver imediatamente que obtemos

exatamente esse valor se calcularmos B(0) para o segundo segmento de curva, baseado em P

1

, P

2

, P

3

e P

4

.

4.8 Exercício

Como pixels normais são muito pequenos, eles não mostram muito claramente quais deles são

selecionados pelos algoritmos de Bresenham. Use uma grade de pontos para simular uma tela com

resolução muito baixa e demonstrar tanto o método drawLine da seção 4.1 (com g como seu primeiro

argumento) quanto o método drawCircle da seção 4.3. Apenas os pontos da grade devem ser usados como

centros dos “superpixels”. Codifique um novo método putPixel para desenhar um pequeno círculo como tal

centro, tendo como diâmetro a distância dGrid entre dois pontos vizinhos da grade.

Não altere os métodos drawLine e drawCircle que desenvolvemos, mas use a distância dGrid, entre

dois pontos vizinhos da grade, no novo método putPixel diferente do mostrado no início da seção 4.1. A

Figura 48 mostra uma grade (com dGrid = 10) e uma reta e um círculo desenhados dessa forma. Assim

como na Figura 30, a reta mostrada aqui tem pontos extremos P(1, 1) e Q(12, 5), mas dessa vez o eixo y

positivo aponta para baixo e a origem é o vértice superior esquerdo do retângulo de desenho. O círculo

possui raio r = 8 e é aproximado pelos mesmos pixels que os mostrados na figura 35 para um oitavo desse

círculo. A reta e o círculo foram produzidos pelos seguintes chamadas aos métodos drawLine e drawCircle

das seções 4.1 e 4.3 (mas com um método putPixel diferente):

(23)

drawLine(g, 1, 1, 12, 5); //g, xP, yP, xQ, yQ drawCircle(g, 23, 10, 8); //g, xC, yC, r

Figura 48: Algoritmos de Bresenham para uma reta e para um círculo

4.9 Resposta do Exercício

O programa a seguir produz apenas a Figura 48. Você deve estendê-lo, permitindo ao usuário

especificar os dois pontos extremos de um segmento de reta e tanto o centro quanto o raio do círculo.

import java.awt.*;

import java.awt.event.*; import java.util.*;

public class Bresenham extends Frame

{ public static void main(String[] args){new Bresenham();}

Bresenham()

{ super("Bresenham");

addWindowListener(new WindowAdapter()

{public void windowClosing(WindowEvent e){System.exit(0);}}); setSize(340, 230);

add("Center", new CvBresenham()); show();

} }

class CvBresenham extends Canvas

{ float rWidth = 10.0F, rHeight = 7.5F, pixelSize; int centerX, centerY, dGrid = 10, maxX, maxY;

void initgr() { Dimension d; d = getSize();

maxX = d.width - 1; maxY = d.height - 1;

pixelSize = Math.max(rWidth/maxX, rHeight/maxY); centerX = maxX/2; centerY = maxY/2;

}

int iX(float x){return Math.round(centerX + x/pixelSize);} int iY(float y){return Math.round(centerY - y/pixelSize);}

void putPixel(Graphics g, int x, int y)

{ int x1 = x * dGrid, y1 = y * dGrid, h = dGrid/2; g.drawOval(x1 - h, y1 - h, dGrid, dGrid);

}

(24)

{ int x = xP, y = yP, D = 0, HX = xQ - xP, HY = yQ - yP, c, M, xInc = 1, yInc = 1;

if (HX < 0){xInc = -1; HX = -HX;} if (HY < 0){yInc = -1; HY = -HY;} if (HY <= HX) { c = 2 * HX; M = 2 * HY; for (;;) { putPixel(g, x, y); if (x == xQ) break; x += xInc; D += M; if (D > HX){y += yInc; D -= c;} } } else { c = 2 * HY; M = 2 * HX; for (;;) { putPixel(g, x, y); if (y == yQ) break; y += yInc; D += M; if (D > HY){x += xInc; D -= c;} } } }

void drawCircle(Graphics g, int xC, int yC, int r) { int x = 0, y = r, u = 1, v = 2 * r - 1, E = 0; while (x < y)

{ putPixel(g, xC + x, yC + y); // NNE putPixel(g, xC + y, yC - x); // ESE putPixel(g, xC - x, yC - y); // SSW putPixel(g, xC - y, yC + x); // WNW x++; E += u; u += 2; if (v < 2 * E){y--; E -= v; v -= 2;} if (x > y) break; putPixel(g, xC + y, yC + x); // ENE putPixel(g, xC + x, yC - y); // SSE putPixel(g, xC - y, yC - x); // WSW putPixel(g, xC - x, yC + y); // NNW }

}

void showGrid(Graphics g)

{ for (int x=dGrid; x<=maxX; x+=dGrid) for (int y=dGrid; y<=maxY; y+=dGrid) g.drawLine(x, y, x, y);

}

public void paint(Graphics g) { initgr(); showGrid(g); drawLine(g, 1, 1, 12, 5); drawCircle(g, 23, 10, 8); } }

Referências

Documentos relacionados

A vivência internacional de estudantes universitários nos programas de mobilidade acadêmica, certamente lhes proporcionam novas experiências, tanto de vida, como

O aumento na produção de matéria seca da parte aérea, com o aumento na disponibilidade de nitrogênio para as plantas, foi devido, principalemnte, ao maior peso de matéria seca

(Henriettea e Henriettella (Melastomataceae; Miconieae) no Rio de Janeiro, Brasil) É apresentado o tratamento taxonômico dos gêneros Henriettea e Henriettella na flora do estado do

Contribuições/Originalidade: A identificação dos atributos que conferem qualidade ao projeto habitacional e das diretrizes de projeto que visam alcançá-los, são de fundamental

(2001), do total de 59 amostras de leite pasteurizado integral, 75,64% das análises conferiram ao menos um parâmetro com valores fora do recomendado, principalmente com relação

• Capacitação e Transferência da metodologia do Sistema ISOR ® para atividades de Coaching e/ou Mentoring utilizando o método das 8 sessões;.. • Capacitação e Transferência

O CFDs disponíveis para negociação com a Companhia são operações à vista do tipo &#34;Non Deliverable&#34; dando a oportunidade de obter lucro sobre as variações das ta- xas

Nesse sentido, este artigo é um trabalho teórico e prático sobre a temática Educação Estatística na Escola Básica articulada com a disciplina de Matemática, tendo como objetivo