Livrojulia Cópia
Livrojulia Cópia
Livrojulia Cópia
Setembro 2020
ii
Conteúdo
Prefácio vii
1 Programação 1
1.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1.1 Linguagens de Programação . . . . . . . . . . . . . . 4
1.1.2 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.2 A Linguagem Julia . . . . . . . . . . . . . . . . . . . . . . . . 6
1.2.1 Sintaxe, Semântica e Pragmática . . . . . . . . . . . . 6
1.2.2 Sintaxe e Semântica do Julia . . . . . . . . . . . . . . . 7
1.2.3 O Avaliador . . . . . . . . . . . . . . . . . . . . . . . . 7
1.3 Elementos da Linguagem . . . . . . . . . . . . . . . . . . . . 8
1.3.1 Números . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.3.2 Combinações . . . . . . . . . . . . . . . . . . . . . . . 9
1.3.3 Avaliação de Combinações . . . . . . . . . . . . . . . 10
1.3.4 Cadeias de Caracteres . . . . . . . . . . . . . . . . . . 10
1.4 Definição de Funções . . . . . . . . . . . . . . . . . . . . . . . 12
1.4.1 Nomes . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.5 Funções Pré-Definidas . . . . . . . . . . . . . . . . . . . . . . 16
1.6 Aritmética em Julia . . . . . . . . . . . . . . . . . . . . . . . . 17
1.7 Avaliação de Nomes . . . . . . . . . . . . . . . . . . . . . . . 19
1.8 Expressões Condicionais . . . . . . . . . . . . . . . . . . . . . 20
1.8.1 Expressões Lógicas . . . . . . . . . . . . . . . . . . . . 20
1.8.2 Valores Lógicos . . . . . . . . . . . . . . . . . . . . . . 20
1.9 Predicados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.9.1 Predicados Aritméticos . . . . . . . . . . . . . . . . . 21
1.10 Operadores Lógicos . . . . . . . . . . . . . . . . . . . . . . . . 21
1.11 Selecção . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
1.12 Selecção Múltipla . . . . . . . . . . . . . . . . . . . . . . . . . 25
1.13 Nomes Locais . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
1.14 Nomes Globais . . . . . . . . . . . . . . . . . . . . . . . . . . 27
1.15 Módulos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
iii
iv CONTEÚDO
2 Modelação 31
2.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2.2 Coordenadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2.3 Operações com Coordenadas . . . . . . . . . . . . . . . . . . 33
2.4 Coordenadas Bidimensionais . . . . . . . . . . . . . . . . . . 36
2.5 Coordenadas Polares . . . . . . . . . . . . . . . . . . . . . . . 38
2.6 Modelação Geométrica Bidimensional . . . . . . . . . . . . . 41
2.7 Efeitos Secundários . . . . . . . . . . . . . . . . . . . . . . . . 45
2.8 Sequenciação . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
2.9 A Ordem Dórica . . . . . . . . . . . . . . . . . . . . . . . . . . 48
2.10 Parametrização de Figuras Geométricas . . . . . . . . . . . . 51
2.11 Documentação . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
2.12 Depuração . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
2.12.1 Erros Sintáticos . . . . . . . . . . . . . . . . . . . . . . 59
2.12.2 Erros Semânticos . . . . . . . . . . . . . . . . . . . . . 59
2.13 Modelação Tridimensional . . . . . . . . . . . . . . . . . . . . 60
2.13.1 Sólidos Pré-Definidos . . . . . . . . . . . . . . . . . . 60
2.14 Coordenadas Cilíndricas . . . . . . . . . . . . . . . . . . . . . 73
2.15 Coordenadas Esféricas . . . . . . . . . . . . . . . . . . . . . . 76
2.16 Modelação de Colunas Dóricas . . . . . . . . . . . . . . . . . 77
2.17 Proporções de Vitrúvio . . . . . . . . . . . . . . . . . . . . . . 79
3 Recursão 89
3.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
3.2 Recursão em Arquitectura . . . . . . . . . . . . . . . . . . . . 94
3.3 Templos Dóricos . . . . . . . . . . . . . . . . . . . . . . . . . . 102
3.4 A Ordem Jónica . . . . . . . . . . . . . . . . . . . . . . . . . . 112
3.5 Recursão na Natureza . . . . . . . . . . . . . . . . . . . . . . 122
4 Estado 129
4.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
4.2 Aleatoriedade . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
4.2.1 Números Aleatórios . . . . . . . . . . . . . . . . . . . 131
4.3 Estado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
4.4 Escolhas Aleatórias . . . . . . . . . . . . . . . . . . . . . . . . 135
4.4.1 Números Aleatórios Fraccionários . . . . . . . . . . . 137
4.4.2 Números Aleatórios num Intervalo . . . . . . . . . . 138
4.5 Planeamento Urbano . . . . . . . . . . . . . . . . . . . . . . . 143
5 Estruturas 153
5.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
5.2 Listas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
5.3 Operações com Listas . . . . . . . . . . . . . . . . . . . . . . . 155
5.3.1 Enumerações . . . . . . . . . . . . . . . . . . . . . . . 156
CONTEÚDO v
6 Formas 191
6.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
6.2 Geometria Construtiva . . . . . . . . . . . . . . . . . . . . . . 191
6.3 Superfícies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
6.3.1 Trifólios, Quadrifólios e Outros Fólios . . . . . . . . . 198
6.4 Álgebra de Formas . . . . . . . . . . . . . . . . . . . . . . . . 204
6.5 Corte de Regiões . . . . . . . . . . . . . . . . . . . . . . . . . 216
6.6 Extrusões . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220
6.6.1 Extrusão Simples . . . . . . . . . . . . . . . . . . . . . 221
6.6.2 Extrusão ao Longo de um Caminho . . . . . . . . . . 232
6.6.3 Extrusão com Transformação . . . . . . . . . . . . . . 235
6.7 Colunas de Gaudí . . . . . . . . . . . . . . . . . . . . . . . . . 235
6.8 Revoluções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240
6.8.1 Superfícies de Revolução . . . . . . . . . . . . . . . . 240
6.8.2 Sólidos de Revolução . . . . . . . . . . . . . . . . . . . 246
6.9 Interpolação de Secções . . . . . . . . . . . . . . . . . . . . . 251
6.9.1 Interpolação por Secções . . . . . . . . . . . . . . . . . 251
6.9.2 Interpolação com Guiamento . . . . . . . . . . . . . . 252
7 Transformações 255
7.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255
7.2 Translação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
7.3 Escala . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
7.4 Rotação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259
7.5 Reflexão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259
7.6 A Ópera de Sydney . . . . . . . . . . . . . . . . . . . . . . . . 260
Epílogo 375
Soluções 381
Prefácio
Este livro nasceu em 2007, após um convite para leccionar uma disciplina
introdutória de Programação aos alunos de Arquitectura do Instituto Su-
perior Técnico (IST). A motivação original para a introdução da disciplina
no curso de Arquitectura era a mesma que para muitos outros cursos: à
semelhança da Matemática e da Física, a Programação tornou-se parte da
formação básica de qualquer aluno do IST.
Com esta premissa, não parecia ser uma disciplina que viesse a desper-
tar grande interesse nos alunos de Arquitectura, particularmente porque
era pouco clara a contribuição que ela pudesse ter para o curso. Para con-
trariar essa impressão inicial, decidi incorporar no programa da disciplina
algumas aplicações da Programação em Arquitectura. Nesse sentido, fui
falar com alunos e docentes de Arquitectura, e pedi-lhes que me explicas-
sem o que faziam e como o faziam. O que vi e ouvi foi revelador.
Apesar dos enormes progressos que as ferramentas de Computer-Aided
Design (CAD) vieram trazer à profissão, a verdade é que a sua utilização
continua manual, laboriosa, repetitiva, aborrecida. A elaboração de um
modelo digital numa ferramenta de CAD implica uma enorme atenção ao
pormenor, impedindo a concentração no fundamental: a ideia. Frequen-
temente, os obstáculos encontrados acabam por forçar o Arquitecto a ter
de simplificar a ideia original. Infelizmente, esses obstáculos não termi-
nam com a criação do modelo. Pelo contrário, agravam-se quando surge a
inevitável necessidade de alterações ao modelo.
Em geral, as ferramentas de CAD são concebidas para facilitar a reali-
zação das tarefas mais comuns, em detrimento de outras menos frequentes
ou mais sofisticadas. Na verdade, para o Arquitecto interessado em mode-
lar formas mais complexas, a ferramenta de CAD poderá apresentar sérias
limitações. E, contudo, essas limitações são apenas aparentes, pois é possí-
vel ultrapassá-las por intermédio da programação. A programação permite
que uma ferramenta de CAD seja ampliada com novas capacidades, elimi-
nando os obstáculos que impedem o trabalho do Arquitecto.
A actividade da programação é intelectualmente muito estimulante,
mas é também um desafio. Implica dominar uma nova linguagem, implica
adoptar uma nova forma de pensar. Frequentemente, esse esforço faz mui-
tos desistirem, mas os que conseguem ultrapassar as dificuldades iniciais
vii
viii PREFÁCIO
Programação
1.1 Introdução
A transmissão de conhecimento é um dos problemas que desde cedo pre-
ocupou a humanidade. Sendo o homem capaz de acumular conhecimento
ao longo de toda a sua vida, é com desânimo que enfrenta a ideia de que,
com a morte, todo esse conhecimento se perca.
Para evitar esta perda, a humanidade inventou toda uma série de me-
canismos de transmissão de conhecimento. O primeiro, a transmissão oral,
consiste na transmissão do conhecimento de uma pessoa para um grupo re-
duzido de outras pessoas, de certa forma transferindo o problema da perda
de conhecimento para a geração seguinte. O segundo, a transmissão es-
crita, consiste em registar em documentos o conhecimento que se pretende
transmitir. Esta forma tem a grande vantagem de, por um lado, poder che-
gar a muitas mais pessoas e, por outro, de reduzir significativamente o
risco de se perder o conhecimento por problemas de transmissão. De facto,
a palavra escrita permite preservar por muito tempo e sem qualquer tipo
de adulteração o conhecimento que o autor pretendeu transmitir.
É graças à palavra escrita que hoje conseguimos compreender e acumu-
lar um vastíssimo conjunto de conhecimentos, muitos deles registados há
milhares de anos atrás.
Infelizmente, nem sempre a palavra escrita conseguiu transmitir com
rigor aquilo que o autor pretendia. A língua natural tem inúmeras ambi-
guidades e evolui substancialmente com o tempo, o que leva a que a inter-
pretação dos textos seja sempre uma tarefa subjectiva. Quer quando escre-
vemos um texto, quer quando o lemos e o interpretamos, existem omissões,
imprecisões, incorrecções e ambiguidades que podem tornar a transmissão
de conhecimento falível. Se o conhecimento que se está a transmitir for
simples, o receptor da informação, em geral, consegue ter a cultura e ima-
ginação suficientes para conseguir ultrapassar os obstáculos. No caso da
transmissão de conhecimentos mais complexos já isso poderá ser muito
1
2 CAPÍTULO 1. PROGRAMAÇÃO
mais difícil.
Quando se exige rigor na transmissão de conhecimento, fazer depen-
der a compreensão desse conhecimento da capacidade de interpretação de
quem o recebe pode ter consequências desastrosas e, de facto, a história
da humanidade está repleta de acontecimentos catastróficos cuja causa é,
tão somente, uma insuficiente ou errónea transmissão de conhecimento,
ou uma deficiente compreensão do conhecimento transmitido.
Para evitar estes problemas, inventaram-se linguagens mais rigorosas.
A matemática, em particular, tem-se obssessivamente preocupado ao longo
dos últimos milénios com a construção de uma linguagem onde o rigor seja
absoluto. Isto permite que a transmissão do conhecimento matemático seja
muito mais rigorosa que nas outras áreas, reduzindo ao mínimo essencial a
capacidade de imaginação necessária de quem está a absorver esse conhe-
cimento.
Para melhor percebermos do que estamos a falar, consideremos um caso
concreto de transmissão de conhecimento, por exemplo, o cálculo do facto-
rial de um número. Se assumirmos, como ponto de partida, que a pessoa a
quem queremos transmitir esse conhecimento já sabe de antemão o que são
os números e as operações aritméticas, podemos dizer-lhe que para calcular
o factorial de um número qualquer, terá de multiplicar todos os números desde a
unidade até esse número. Infelizmente, esta descrição é demasiado extensa
e, pior, é pouco rigorosa, pois não dá ao ouvinte a informação de que os
números que ele tem de multiplicar são apenas os números inteiros. Para
evitar estas imprecisões e, simultaneamente, tornar mais compacta a infor-
mação a transmitir, a Matemática inventou todo um conjunto de símbolos
e conceitos cujo significado deve ser compreendido por todos. Por exem-
plo, para indicar a sequência de números inteiros entre 1 e 9, a Matemática
permite-nos escrever 1, 2, 3, . . . , 9. Do mesmo modo, para evitarmos falar
de “um número qualquer,” a Matemática inventou o conceito de variável: um
nome que designa qualquer “coisa” e que pode ser reutilizado em várias
partes de uma afirmação matemática com o significado óbvio de repre-
sentar sempre essa mesma “coisa.” Deste modo, a linguagem Matemática
permite-nos formular a mesma afirmação sobre o cálculo do factorial nos
seguintes termos:
n! = 1 × 2 × 3 × · · · × n
2! = 1 × 2 × 3 × · · · × 2
Neste caso, o cálculo deixa de fazer sentido, o que mostra que, na ver-
dade, a imaginação necessária para a interpretação da fórmula não se res-
tringe apenas às reticências mas sim a toda a fórmula: o número de termos
a considerar depende do número do qual queremos saber o factorial.
Admitindo que o nosso leitor teria tido a imaginação suficiente para
descobrir esse detalhe, ele conseguiria tranquilamente calcular que 2! =
1 × 2 = 2. Ainda assim, casos haverá que o deixarão significativamente
menos tranquilo. Por exemplo, qual é o factorial de zero? A resposta não
parece óbvia. E quanto ao factorial de −1? Novamente, não está claro. E
quanto ao factorial de 4.5? Mais uma vez, a fórmula nada diz e a nossa
imaginação também não consegue adivinhar.
Será possível encontrar uma forma de transmitir o conhecimento da
função factorial que minimize as imprecisões, lacunas e ambiguidades? Ex-
perimentemos a seguinte variante da definição da função factorial:
(
1, se n = 0
n! =
n · (n − 1)!, se n ∈ N.
Teremos agora atingido o rigor suficiente que dispensa imaginação por
parte do leitor? A resposta estará na análise dos casos que causaram pro-
blemas à definição anterior. Em primeiro lugar, não existem reticências, o
que é positivo. Em segundo lugar, para o factorial de 2 temos, pela defini-
ção:
2! = 2 × 1! = 2 × (1 × 0!) = 2 × (1 × 1) = 2 × 1 = 2
ou seja, não há qualquer ambiguidade. Finalmente, vemos que não faz
sentido determinar o factorial de −1 ou de 4.5 pois a definição apresentada
apenas se aplica a um determinado conjunto de entidades, nomeadamente,
aos membros de N0 .
Este exemplo mostra que, mesmo na matemática, há diferentes graus
de rigor nas diferentes formas como se expõe o conhecimento. Algumas
dessas formas exigem um pouco mais de imaginação e outras um pouco
menos mas, em geral, qualquer delas tem sido suficiente para que a huma-
nidade tenha conseguido preservar o conhecimento adquirido ao longo da
sua história.
Acontece que, actualmente, a humanidade conta com um parceiro que
tem dado uma contribuição gigantesca para o seu progresso: o computa-
dor. Esta máquina tem a extraordinária capacidade de poder ser instruída
4 CAPÍTULO 1. PROGRAMAÇÃO
1.1.2 Exercícios
Exercício 1.1.1 A exponenciação bn é uma operação entre dois números b
e n designados base e expoente, respectivamente. Quando n é um inteiro
6 CAPÍTULO 1. PROGRAMAÇÃO
bn = |b × b ×{z· · · × }b
n
1.2.3 O Avaliador
julia>
de dados, sendo que cada tipo de dados emprega uma sintaxe específica
para a descrição dos seus elementos e permite a sua combinação usando
operações específicas.
1.3.1 Números
Um número é uma das entidades mais básicas da linguagem Julia. Em
Julia, os números podem ser exactos ou inexactos. Os números exactos in-
cluem os inteiros, como o 7. Os números inexactos são tipicamente escritos
em notação decimal ou científica, como por exemplo, 123.45 ou 1.2345e2.
Em termos de semântica, um número é considerado um conceito funda-
mental, de tal forma que o significado de um número é o próprio número.
É precisamente esse o resultado que vemos quando experimentamos os nú-
meros na REPL do Julia:
julia> 1
1
julia> 12345
12345
julia> 4.5
4.5
1.3.2 Combinações
Uma combinação é uma expressão que descreve a aplicação de um opera-
dor aos seus operandos. Na matemática, os números podem ser combina-
dos usando operações como a soma ou o produto. Como exemplo, temos
1 + 2 e 1 + 2 × 3. A soma e o produto de números são operações elemen-
tares denominadas procedimentos primitivos. A sintaxe e semântica do
Julia para as expressões aritméticas é muito semelhante à da Matemática,
e segue as mesmas precedências entre operadores. Isto quer dizer que, em
Julia, 1+2*3 é interpretado como 1 + (2 × 3).
A semântica de uma combinação é obtida através da aplicação da ope-
ração (ou operações) aos seus operandos:
julia> 1+2
3
julia> 12345+54321
66666
julia> 12345*54321
670592745
julia> 1/2
0.5
julia> 1+2*3
7
julia> (1+2)*3
9
1. 1/2*3
2. 1/(2 - 3)
3. (1 + 2)/3
4. 1/2/3
5. 1/(2/3)
julia> 2*3
6
julia> 1 + 2*3
7
1. 1/2*3
2. 1*(2 - 3)
3. (1 + 2)/3
4. 1 - 2 - 3
Sequência Resultado
\\ o carácter \ (backslash)
\" o carácter " (aspas)
\e o carácter escape
\n o carácter de mudança de linha (newline)
\r o carácter de mudança de linha (carriage return)
\t o carácter de tabulação (tab)
julia> "Olá"
"Olá"
quadrado(quadrado(1 + 2))
↓
quadrado(quadrado(3))
↓
quadrado(3*3)
↓
quadrado(9)
↓
9*9
↓
81
area_circulo(raio) =
3.14159*quadrado(raio)
area_circulo(2)
↓
3.14159*quadrado(2)
↓
3.14159*(2*2)
↓
3.14159*4
↓
12.5664
1.4.1 Nomes
A definição de funções em Julia passa pela utilização de nomes: nomes
para as funções e nomes para os parâmetros das funções.
Em Julia, existem limitações para a escrita de nomes. Estes só podem
ser compostos de letras, dígitos, e pelo carácter _, e não podem começar
por um dígito. Na prática, a criação de nomes segue algumas regras que
convém ter presentes:
b·c
A(b, c) =
2
Em Julia, teremos:
A(b, c) =
b*c/2
area_triangulo(base, altura) =
base*altura/2
Exercício 1.4.3 Defina a função radianos que recebe uma quantidade an-
gular em graus e calcula o valor correspondente em radianos. Note que 180
graus correspondem a π radianos.
Exercício 1.4.4 Defina a função graus que recebe uma quantidade angu-
lar em radianos e calcula o valor correspondente em graus.
Exercício 1.4.5 Defina a função que calcula o perímetro de uma circunfe-
rência de raio r.
16 CAPÍTULO 1. PROGRAMAÇÃO
cos4 √2
5
2. atan 3
1
√ 5
3. 2 + 3 + sin 2 2
1. log(sin(2^4 + floor(atan(pi))/sqrt(5)))
2. cos(cos(cos(0.5)))^5
1.6. ARITMÉTICA EM JULIA 17
3. sin(cos(sin(pi/3)/3)/3)
julia> 10.0^10
1.0e10
julia> 10.0^100
1e+100
julia> 10.0^1000
Inf
julia> Inf + 1
Inf
julia> Inf - 1
Inf
julia> -Inf - 1
-Inf
julia> ^
^ (generic function with 63 methods)
julia> abs
abs (generic function with 13 methods)
1.9 Predicados
No caso mais usual, uma expressão lógica envolve uma operação com de-
terminados argumentos. Nesta situação, a operação é denominada predi-
cado e, quando aplicada aos seus operandos, produz um valor que é in-
terpretado como sendo verdadeiro ou falso. O predicado é, consequente-
mente, uma operação que produz verdade ou falso.
julia> 4 > 3
true
julia> 4 < 3
false
julia> 2 + 3 <= 6 - 1
true
2. ! (1 == 2 || 2 == 3)
3. 1 < 2 || 1 == 2 || 1 > 2
1. x < y
2. x ≤ y
3. x < y ∧ y < z
4. x < y ∧ x < z
5. x ≤ y ≤ z
6. x ≤ y < z
7. x < y ≤ z
1.11 Selecção
Se observarmos a definição matemática da função valor absoluto
(
−x, se x < 0
|x| =
x, caso contrário.
que, em linguagem natural, se traduz para “se expressão lógica, então expres-
são consequente, caso contrário, expressão alternativa.”
A avaliação de uma expressão condicional é feita através da avaliação
da expressão lógica que, se produzir verdade, implica a avaliação da expressão
consequente e, se produzir falso, implica a avaliação da expressão alternativa.
1.11. SELECÇÃO 23
abs(x) =
if x < 0
-x
else
x
end
abs(x) =
x < 0 ? -x : x
max(x, y) =
if x > y
x
else
y
end
Ou, alternativamente:
24 CAPÍTULO 1. PROGRAMAÇÃO
max(x, y) = x > y ? x : y
signum(x) = x < 0 ? -1 : x == 0 ? 0 : 1
signum(x) = x < 0 ? -1 : (x == 0 ? 0 : 1)
signum(x) =
if x < 0
-1
else
if x == 0
0
else
1
end
end
1.12. SELECÇÃO MÚLTIPLA 25
signum(x) =
if x < 0
-1
elseif x == 0
0
else
1
end
Exercício 1.12.1 Defina uma função soma_maiores que recebe três núme-
ros como argumento e determina a soma dos dois maiores.
Exercício 1.12.2 Defina a função max3 que recebe três números como ar-
gumento e calcula o maior entre eles.
Exercício 1.12.3 Defina a função segundo_maior que recebe três números
como argumento e devolve o segundo maior número, i.e., que está entre o
maior e o menor.
c b
e tentemos definir uma função Julia que calcula a área do triângulo a partir
dos parâmetros a, b e c.
Uma das formas de calcular a área do triângulo baseia-se na famosa
fórmula de Heron:2
2
Heron de Alexandria foi um importante matemático e engenheiro Grego do primeiro
século depois de Cristo a quem se atribuem inúmeras descobertas e invenções, incluindo a
máquina a vapor e a seringa.
26 CAPÍTULO 1. PROGRAMAÇÃO
p
A= s (s − a) (s − b) (s − c)
em que é s o semi-perímetro do triângulo:
a+b+c
s=
2
Ao tentarmos usar a fórmula de Heron para definir a correspondente
função Julia, somos confrontados com uma dificuldade: a fórmula está es-
crita (também) em termos do semi-perímetro s, mas s não é um parâmetro,
é um valor derivado dos parâmetros do triângulo.
Uma das maneira de ultrapassarmos a dificuldade consiste em eliminar
o nome s, substituindo-o pelo seu significado:
s
a+b+c a+b+c a+b+c a+b+c
A= −a −b −c
2 2 2 2
pi = 3.14159
1.15 Módulos
module Areas
export area_circulo
pi = 3.14159
quadrado(x) =
x*x
area_circulo(r) =
pi*quadrado(r)
end
using Main.Areas
volume_cilindro(r, h) =
area_circulo(r)*h
ex − e−x
sinh x =
2
ex + e−x
cosh x =
2
ex − e−x
tanh x =
ex + e−x
Exercício 1.15.2 Defina, no módulo da pergunta anterior, as funções in-
versas do seno hiperbólico (asinh), cosseno hiperbólico (acosh) e tangente
hiperbólica (atanh) cujas definições matemáticas são:
p
asinh x = sinh−1 x = ln(x + x2 + 1)
3
Nesta obra não iremos discutir o processo de instalação de módulos. O leitor interes-
sado em criar os seus próprios módulos deverá consultar a documentação oficial da lingua-
gem Julia.
1.15. MÓDULOS 29
p
acosh x = cosh−1 x = ± ln(x + x2 − 1)
(
1
−1 ln( 1+x
1−x ), se |x| < 1
atanh x = tanh x = 21 x+1
2 ln( x−1 ), se |x| > 1
30 CAPÍTULO 1. PROGRAMAÇÃO
Capítulo 2
Modelação
2.1 Introdução
Vimos, nas secções anteriores, alguns dos tipos de dados pré-definidos em
Julia. Em muitos casos, esses tipos de dados são os necessários e suficientes
para nos permitir criar os nossos programas. Noutros casos, será necessá-
rio introduzirmos novos tipos de dados. Nesta secção iremos estudar um
novo tipo de dados que nos será particularmente útil para a modelação de
entidades geométricas: coordenadas.
2.2 Coordenadas
A Arquitectura pressupõe a localização de elementos no espaço. Essa lo-
calização expressa-se em termos do que se designa por coordenadas: cada
coordenada é um número e uma sequência de coordenadas identifica uni-
vocamente um ponto no espaço. A Figura 2.1 esquematiza uma possível
sequência de coordenadas (x, y, z) que identificam o ponto P num espaço
tridimensional. Diferentes sistemas de coordenadas são possíveis e, no caso
da Figura 2.1, estamos a empregar aquele que se designa por sistema de
coordenadas rectangulares, também conhecido por sistema de coordenadas
Cartesianas, em honra do seu inventor: René Descartes.1
Existem várias operações úteis que podemos realizar com coordenadas.
Por exemplo, podemos calcular a distância entre duas posições no espaço
P = (px , py , pz ) e Q = (qx , qy , qz ). Essa distância é determinada pela fór-
mula q
d= (qx − px )2 + (qy − py )2 + (qz − pz )2
31
32 CAPÍTULO 2. MODELAÇÃO
Y
P = (Px , Py , Pz )
Pz
Px
Py
X
julia> distancia(2, 1, 3, 5, 6, 4)
5.916079783099616
p x + qx p y + qy p z + qz
( , , )
2 2 2
mas é difícil conceber uma função que a implemente pois, aparentemente,
teria de calcular simultâneamente três resultados diferentes, um para cada
uma das coordenadas X, Y , e Z.
Para lidar com estes problemas, os matemáticos inventaram o conceito
de tuplo. Informalmente, um tuplo é apenas um agrupamento de valores
2.3. OPERAÇÕES COM COORDENADAS 33
mas, em inúmeros casos, este agrupamento acaba por ter um nome especí-
fico. Um número racional, por exemplo, é um tuplo que agrupa dois nú-
meros inteiros, denominados o numerador e o denominador. Do mesmo
modo, uma posição no espaço é um tuplo, pois agrupa três coordenadas.
Todas as linguagem de programação disponibilizam mecanismos para
permitir a criação e manipulação de tuplos. No caso da linguagem Julia,
para além dos tuplos já pré-definidos, foram definidos no módulo Khepri
outros tuplos que nos serão úteis para a modelação geométrica.
Para os podermos usar, temos de requerer o módulo Khepri.
using Khepri
julia> xyz(1, 2, 3)
xyz(1, 2, 3)
julia> xyz(2*3, 4 + 1, 6 - 2)
xyz(6, 5, 4)
distancia(p, q) =
sqrt((qx - px)^2 + (qy - py)^2 + (qz - pz)^2)
cx(xyz(x, y, z )) = x
cy(xyz(x, y, z )) = y
cz(xyz(x, y, z )) = z
distancia(p, q) =
sqrt((cx(q) - cx(p))^2 + (cy(q) - cy(p))^2 + (cz(q) - cz(p))^2)
Para simplificar o uso das funções cx, cy, e cz, o Khepri disponibiliza
uma sintaxe alternativa mais próxima da tradição matemática. Usando esta
sintaxe, as expressões cx(p), cy(p), e cz(p) podem ser escritas como p.x,
p.y, e p.z, respectivamente, o que permite simplificar a definição da fun-
ção distância:
distancia(p, q) =
sqrt((q.x - p.x)^2 + (q.y - p.y)^2 + (q.z - p.z)^2)
Z
P0
V
∆z
P V
Y ∆z
z z
∆x ∆y
y ∆y
x
y
∆x
X
xy(x, y) =
xyz(x, y, 0)
vxy(x, y) =
vxyz(x, y, 0)
P1
...
P0
38 CAPÍTULO 2. MODELAÇÃO
d
2π
n
X
ρ
y
φ
x
pol_fi(c) =
atan2(cy(c), cx(c))
P0
ρ
φ
P
julia> pol(1, 0)
xyz(1, 0, 0)
julia> pol(sqrt(2), pi/4)
xyz(1.0000000000000002, 1.0, 0)
julia> pol(1, pi/2)
xyz(6.123233995736766e-017, 1.0, 0)
julia> pol(1, pi)
xyz(-1.0, 1.2246467991473532e-016, 0)
vpol(ro, fi) =
vxy(ro*cos(fi), ro*sin(fi))
Como se pode ver, embora as coordenadas não sejam iguais, elas são
bastante próximas, ou seja, a distância entre elas é muito próxima de zero.
Proponha uma nova definição para a função posicoes_iguais baseada no
conceito de distância entre as coordenadas.
using Khepri
backend(autocad)
ou com
using Khepri
backend(rhino)
using Khepri
backend(autocad)
circle(pol(0, 0), 4)
circle(pol(4, pi/4), 2)
circle(pol(6, pi/4), 1)
polygon(pol(1, 2*pi*0/5),
pol(1, 2*pi*1/5),
pol(1, 2*pi*2/5),
pol(1, 2*pi*3/5),
pol(1, 2*pi*4/5))
circle(xy(1, 2), 3)
r r
P P
2.8 Sequenciação
Até agora, temos combinado expressões matemáticas usando operadores
matemáticos. Por exemplo, a partir das expressões sin(x) e cos(x), po-
demos calcular a sua divisão através de sin(x)/cos(x). Isto é possível
porque a avaliação das subexpressões sin(x) e cos(x) irá produzir dois
valores que podem então ser usados para fazer a divisão.
Já no caso de expressões envolvendo as funções geométricas, a sua com-
binação tem de ser realizada de forma diferente pois, como vimos, a sua
avaliação também produz efeitos secundários. Como é precisamente a re-
alização desses efeitos que nos interessa, o Julia providencia uma forma
de os realizarmos sequencialmente, i.e., uns a seguir aos outros. A título
de exemplo, consideremos uma função que desenha um círculo de raio r
centrado em P com um quadrado inscrito ou circunscrito, dependendo de
uma escolha do utilizador, tal como apresentamos na Figura 2.11.
A definição desta função poderá começar por algo do género:
circulo_quadrado(p, r, inscrito) =
...
circulo_quadrado(p, r, inscrito) =
if inscrito
cria um círculo e um quadrado inscrito no círculo
else
cria um círculo e um quadrado circunscrito ao círculo
end
circulo_quadrado(p, r, inscrito) =
if inscrito
begin
cria um círculo
cria um quadrado inscrito no círculo
end
else
begin
cria um círculo
cria um quadrado circunscrito ao círculo
end
circulo_quadrado(p, r, inscrito) =
if inscrito
cria um círculo
cria um quadrado inscrito no círculo
else
cria um círculo
cria um quadrado circunscrito ao círculo
end
circulo_quadrado(p, r, inscrito) =
if inscrito
circle(p, r)
rectangle(p + vpol(r, 5/4*pi), p + vpol(r, 1/4*pi))
else
circle(p, r)
rectangle(p + vxy(-r, -r), p + vxy(r, r))
end
circulo_quadrado(p, r, inscrito) =
begin
circle(p, r)
if inscrito
rectangle(p + vpol(r, 5/4*pi), p + vpol(r, 1/4*pi))
else
rectangle(p + vxy(-r, -r), p + vxy(r, r))
end
end
6
Estas colunas apresentam ainda uma deformação intencional denominada entasis. A
entasis consiste em dar uma ligeira curvatura à coluna e, segundo alguns autores, destina-
se a corrigir uma ilusão de óptica que faz as colunas direitas parecerem encurvadas.
50 CAPÍTULO 2. MODELAÇÃO
y
(−1, 11) (1, 11)
(−1, 10.5) (1, 10.5)
(−0.8, 10) (0.8, 10)
x
(−1, 0) (0, 0) (1, 0)
fuste() =
line(xy(-0.8, 10),
xy(-1, 0),
xy(1, 0),
xy(0.8, 10),
xy(-0.8, 10))
Neste exemplo, usámos a função line que, dada uma sequência de po-
sições, constrói uma linha com vértices nesses pontos. Uma outra possibili-
dade, eventualmente mais correcta, seria a criação de uma linha poligonal
fechada, algo que podemos fazer com a função polygon, omitindo a posição
repetida, i.e.:
fuste() =
polygon(xy(-0.8, 10),
xy(-1, 0),
xy(1, 0),
xy(0.8, 10))
P1 = (x − rt , y + a) rt P2 = (x + rt , y + a)
P = (x, y)
P4 = (x − rb , y) rb P3 = (x + rb , y)
P2 = (x − rt , y + a) rt P3 = (x + rt , y + a)
P = (x, y) a
P1 = (x − rb , y) rb P4 = (x + rb , y)
P2 = (x + 2l , y + a)
P = (x, y) a
P1 = (x − 2l , y) l
la
aa
rbc ac
af
P
rbf
Tal como fizemos anteriormente, vamos dar nomes mais claros aos pa-
râmetros da Figura 2.18. Usando os nomes p, a_fuste, r_base_fuste,
a_coxim, r_base_coxim, a_abaco e l_abaco no lugar de, respectivamente,
P , af , rbf , ac , rbc , aa e la , temos:
2.11. DOCUMENTAÇÃO 55
coluna(p,
a_fuste, r_base_fuste,
a_coxim, r_base_coxim,
a_abaco, l_abaco) =
begin
fuste(p, a_fuste, r_base_fuste, r_base_coxim)
coxim(p + vxy(0, a_fuste), a_coxim, r_base_coxim, l_abaco/2)
abaco(p + vxy(0, a_fuste + a_coxim), a_abaco, l_abaco)
end
Como é óbvio pela análise desta figura, nem todas as colunas desenha-
das obedecem aos cânones da ordem Dórica. Mais à frente iremos ver que
modificações serão necessárias para evitar este problema.
2.11 Documentação
Na função coluna, a_fuste é a altura do fuste, r_base_fuste é o raio
da base do fuste, r_topo_fuste é o raio do topo do fuste, a_coxim é a
56 CAPÍTULO 2. MODELAÇÃO
• O programa Julia deve ser suficientemente claro para que um ser hu-
mano o consiga perceber. É sempre preferível perder mais tempo a
tornar o programa claro do que a escrever documentação que o expli-
que.
"
Desenha o fuste de uma coluna dorica.
"
Desenha o coxim de uma coluna dorica.
"
Desenha o abaco de uma coluna dorica.
"
Desenha uma coluna dorica composta por fuste, coxim e abaco.
β σ
ρ
α
P
Area:18.00
2.12 Depuração
Como sabemos, errare humanum est. O erro faz parte do nosso dia-a-dia e,
por isso, em geral, sabemos lidar com ele. Já o mesmo não se passa com
2.12. DEPURAÇÃO 59
julia> 1 + "dois"
ERROR: MethodError: no method matching +(::Int64, ::String)
h1
α1
h
h0
α0
l0 l1
l
P
l
X
tico monumental ladeado por duas torres idênticas. Cada torre é de forma
cubóide, com a base e o topo rectangulares e as restantes quatro faces tra-
pezoidais. Defina uma função convenientemente parametrizada capaz de
criar uma representação simplificada de um pilone, semelhante ao que se
apresenta na seguinte imagem:
z
y
ψ ρ
y
φ
p
p y x2 + y 2
( x2 + y 2 + z 2 , atan , atan )
x z
8
A colatitude é o ângulo complementar à latitude, i.e., a diferença entre o pólo ( π2 ) e a
latitude.
2.16. MODELAÇÃO DE COLUNAS DÓRICAS 77
coluna(p,
a_fuste, r_base_fuste,
a_coxim, r_base_coxim,
a_abaco, l_abaco) =
begin
fuste(p, a_fuste, r_base_fuste, r_base_coxim)
coxim(p + vz(a_fuste), a_coxim, r_base_coxim, l_abaco/2)
abaco(p + vz(a_fuste + a_coxim), a_abaco, l_abaco)
end
• A largura das colunas, na base, será de dois modulos e a sua altura, incluindo
os capitéis, será de catorze.
Daqui se deduz que um módulo iguala o raio da base da coluna e que
a altura da coluna deverá ser 14 vezes esse raio. Dito de outra forma,
1
o raio da base da coluna deverá ser 14 da altura da coluna.
• A altura do capitel será de um módulo e a sua largura de dois módulos e um
sexto.
Isto implica que a altura do coxim somada à do ábaco será um mó-
dulo, ou seja, igual ao raio da base da coluna e a largura do ábaco será
de 2 61 módulos ou 136 do raio. Juntamente com o facto de a altura da
coluna ser de 14 módulos, implica ainda que a altura do fuste será de
13 vezes o raio.
• Seja a altura do capitel dividida em três partes, das quais uma formará o
ábaco com o seu cimáteo, o segundo o équino (coxim) com os seus aneis e o
terceiro o pescoço.
Isto quer dizer que o ábaco tem uma altura de um terço de um mo-
dulo, ou seja 13 do raio da base, e o coxim terá os restantes dois terços,
ou seja, 23 do raio da base.
raio_topo_fuste(raio_base, altura) =
if altura < 15
5.0/6.0*raio_base
else
...
end
raio_topo_fuste(raio_base, altura) =
if altura < 15
5.0/6.0*raio_base
elseif altura >= 15 && altura < 20
5.5/6.5*raio_base
else
...
end
10
O pé foi a unidade fundamental de medida durante inúmeros séculos, mas a sua real
dimensão variou ao longo do tempo. O comprimento do pé internacional é de 304.8 milí-
metros e foi estabelecido, por acordo, em 1958. Antes disso, vários outros comprimentos
foram usados, como o pé Dórico de 324 milímetros, os pés Jónico e Romano de 296 milíme-
tros, o pé Ateniense de 315 milímetros, os pés Egípcio e Fenício de 300 milímetros, etc.
84 CAPÍTULO 2. MODELAÇÃO
raio_topo_fuste(raio_base, altura) =
if altura < 15
5.0/6.0*raio_base
elseif altura < 20
5.5/6.5*raio_base
else
...
50 end
raio_topo_fuste(raio_base, altura) =
40 if altura < 15
5.0/6.0*raio_base
6 21
elseif altura < 20
7 21 5.5/6.5*raio_base
elseif altura < 30
30 6.0/7.0*raio_base
elseif altura < 40
6 6.5/7.5*raio_base
7 elseif altura < 50
7.0/8.0*raio_base
20 else
5 21 ...
6 21 end
15
O problema agora é que Vitrúvio deixou a porta aberta para colunas
5 arbitrariamente altas, dizendo simplesmente que, no caso de colunas mais al-
6 tas, determine-se proporcionalmente a diminuição com base nos mesmos princípios.
Para percebermos claramente de que princípios estamos a falar, considere-
mos a evolução da relação entre o topo e a base das colunas que é visível
0 na imagem lateral.
A razão entre o raio do topo da coluna e o raio da base da coluna
é, tal como se pode ver na margem (e já era possível deduzir da função
raio_topo_fuste), uma sucessão da forma
5 5 12 6 6 12 7
, , , , , ···
6 6 12 7 7 12 8
Torna-se agora óbvio que, para colunas mais altas, “os mesmos princí-
pios” de que Vitrúvio fala se resumem a, para cada 10 pés adicionais, somar
1
2 quer ao numerador, quer ao denominador. No entanto, é importante re-
parar que este princípio só pode ser aplicado a partir dos 15 pés de altura,
pois o primeiro intervalo é maior que os restantes. Assim, temos de tra-
tar de forma diferente as colunas até aos 15 pés e, daí para a frente, basta
subtrair 20 pés à altura e determinar a divisão inteira por 10 para saber o
número de vezes que precisamos de somar 12 quer ao numerador quer ao
denominador de 67 .
2.17. PROPORÇÕES DE VITRÚVIO 85
É este “tratar de forma diferente” um caso e outro que, mais uma vez,
sugere a necessidade de um mecanismo de selecção: é necessário distinguir
dois casos e reagir em conformidade para cada um. No caso da coluna de
Vitrúvio, se a coluna tem uma altura a até 15 pés, a razão entre o topo e a
base é r = 56 ; se a altura a não é inferior a 15 pés, a razão r entre o topo e a
base será:
6 + b a−20 1
10 c · 2
r=
7 + b a−20 1
10 c · 2
6 + b a−20
10 c ·
1
2 12 + b a−20
10 c
a
12 + b 10 c−2 a
10 + b 10 c
r= a−20 1 = a−20 = a = a
7 + b 10 c · 2 14 + b 10 c 14 + b 10 c − 2 12 + b 10 c
raio_topo_fuste(raio_base, altura) =
if altura < 15
5/6*raio_base
else
let divisoes = floor(altura/10)
(10 + divisoes)/(12 + divisoes)*raio_base
end
end
coluna_dorica(p, altura) =
let r_base_fuste = altura/14,
r_base_coxim = raio_topo_fuste(r_base_fuste, altura),
a_fuste = 13*r_base_fuste,
a_coxim = 2/3*r_base_fuste,
a_abaco = 1/3*r_base_fuste,
l_abaco = 13/6*r_base_fuste
fuste(p, a_fuste, r_base_fuste, r_base_coxim)
coxim(p + vz(a_fuste), a_coxim, r_base_coxim, l_abaco/2)
abaco(p + vz(a_fuste + a_coxim), a_abaco, l_abaco)
end
coluna_dorica(p, altura) =
let r_base_fuste = altura/14,
r_base_coxim = raio_topo_fuste(r_base_fuste, altura),
a_fuste = 13*r_base_fuste,
a_coxim = 2/3*r_base_fuste,
a_abaco = 1/3*r_base_fuste,
l_abaco = 13/6*r_base_fuste
coluna(p, a_fuste, r_base_fuste, a_coxim, r_base_coxim, a_abaco, l_abaco)
end
Recursão
3.1 Introdução
Vimos que as nossas funções, para fazerem algo útil, precisam de invocar
outras funções. Por exemplo, se já tivermos a função que calcula o qua-
drado de um número e pretendermos definir a função que calcula o cubo
de um número, podemos facilmente fazê-lo à custa do quadrado e de uma
multiplicação adicional, i.e.:
cubo(x) = quadrado(x) * x
quarta_potencia(x) = cubo(x) * x
potencia(x, n) = potencia_inferior(x, n) * x
89
90 CAPÍTULO 3. RECURSÃO
potencia(x, n) =
if n == 0
1
else
potencia(x, n - 1) * x
end
factorial(-1)
↓
-1*factorial(-2)
↓
-1*-2*factorial(-3)
3.1. INTRODUÇÃO 93
↓
-1*-2*-3*factorial(-4)
↓
-1*-2*-3*-4*factorial(-5))
↓
-1*-2*-3*-4*-5*factorial(-6)
↓
...
julia> factorial(3)
6
julia> factorial(-1)
RuntimeError: maximum recursion depth exceeded
1. ackermann(0, 8)
2. ackermann(1, 8)
3. ackermann(2, 8)
4. ackermann(3, 8)
5. ackermann(4, 8)
94 CAPÍTULO 3. RECURSÃO
···
c
P
Figura 3.1: Perfil de uma escada com n degraus cujo primeiro degrau co-
meça no ponto P e cujos degraus possuem um cobertor c e um espelho
e.
···
P0 n−1
P n
escada(p, c, e, n) =
if n == 0
nothing
else
degrau(p, c, e)
escada(p + vxy(c, e), c, e, n - 1)
end
piramide_degraus(p, b, t, h, d, n) =
if n == 0
nothing
else
regular_pyramid_frustum(4, p, b, 0, h, t)
piramide_degraus(p + vz(h), b - d, t - d, h, d, n - 1)
end
d
t h
P b
Exercício 3.2.2 O arco falso é a mais antiga forma de arco, sendo formado
por paralelipípedos dispostos horizontalmente em degraus formando uma
98 CAPÍTULO 3. RECURSÃO
abertura que se vai estreitando até ao topo, terminando com uma viga ho-
rizontal, tal como se apresenta no seguinte esquema:
∆e e l
P
sua função deverá ter como parâmetros o centro e o raio do círculo maior
e, ainda, o factor de redução f .
Exercício 3.2.4 Considere o desenho de círculos tal como apresentado na
seguinte imagem:
r1
r0 ∆φ
φ
x
p
Exercício 3.2.6 Defina uma função circulos capaz de criar a figura apre-
sentada em seguida:
3.2. RECURSÃO EM ARQUITECTURA 101
a
P
c
Exercício 3.2.8 Defina uma função losangos capaz de criar a figura apre-
sentada em seguida:
102 CAPÍTULO 3. RECURSÃO
α
P
α
P
~v
P
Q
do cálculo desse vector a partir dos pontos inicial e final da fileira de colu-
nas. Usando a função colunas_doricas, defina uma função denominada
colunas_doricas_entre que, dados os centros P e Q da base das colunas
inicial e final, a altura h das colunas e, finalmente, o número de colunas,
cria a fileira de colunas entre aqueles dois centros.
A título de exemplo, a imagem seguinte mostra o resultado da avaliação
das seguintes expressões:
rp
∆ab
∆rb
x
rb
Figura 3.9: Corte da base de um Tholos. A base é composta por uma sequên-
cia de cilindros sobrepostos cujo raio de base rb encolhe de ∆rb a cada de-
grau e cuja altura incrementa ∆ab a cada degrau.
ap
ab rp
y
rb rp
∆φ
φ
1
Para visualizar a imagem estereoscópica, foque a atenção no meio das duas imagens e
cruze os olhos, como se quisesse focar um objecto muito próximo. Irá reparar que as duas
imagens passaram a ser quatro, embora ligeiramente desfocadas. Tente então alterar o cru-
zar dos olhos de modo a só ver três imagens, i.e., até que as duas imagens centrais fiquem
sobrepostas. Concentre-se nessa sobreposição e deixe os olhos relaxarem até a imagem ficar
focada.
112 CAPÍTULO 3. RECURSÃO
Defina uma função que, a partir do centro da cidade e do raio dos cilin-
dros centrais constrói uma cidade semelhante à representada.
α
P1 ~v0
r·f ~v1 ~v P
2 r·f ·f ·f
P2
P3
r·f ·f
espiral(p, r, a, n, f) =
if n == 0
nothing
else
quarto_circunferencia(p, r, a)
espiral(p + vpol(r*(1 - f), a + pi/2), r*f, a + pi/2, n - 1, f)
end
quarto_circunferencia(p, r, a) =
begin
arc(p, r, a, pi/2)
line(p, p + vpol(r, a))
line(p, p + vpol(r, a + pi/2))
end
∆α
r
α
Figura 3.16: Várias espirais com razões de redução de 0.9, 0.7 e 0.5, respec-
tivamente.
2
Note-se que se trata, tão somente, de aproximações. Os processos originais eram bas-
tante mais complexos.
3.4. A ORDEM JÓNICA 119
Figura 3.18: Várias espirais com razão de redução de 0.8 por volta e incre-
mento de ângulo de π, π2 e π4 , respectivamente.
r1
r2 r2
α α
P
r0
3.4. A ORDEM JÓNICA 121
Defina uma função ovo que desenha um ovo. A função deverá receber,
apenas, as coordenadas do ponto P , os raios r0 e r1 e, finalmente, a altura
h do ovo.
ramos de uma árvore são como pequenas árvores que emanam do tronco.
Como se pode ver da Figura 3.19, de cada ramo de uma árvore emanam
outras pequenas árvores, num processo que se repete até se atingir uma
dimensão suficientemente pequena para que apareçam outras estruturas,
como folhas, flores, frutos, pinhas, etc.
Se, de facto, uma árvore possui uma estrutura recursiva, então deverá
ser possível “construir” árvores através de funções recursivas. Para tes-
tarmos esta teoria, vamos começar por considerar uma versão muito sim-
plista de uma árvore, em que temos um tronco que, a partir de uma certa
altura, se divide em dois. Cada um destes subtroncos cresce fazendo um
certo ângulo com o tronco de onde emanou e atinge um comprimento que
deverá ser uma fracção do comprimento desse tronco, tal como se apre-
senta na Figura 3.20. O caso de paragem ocorre quando o comprimento do
tronco se tornou tão pequeno que, em vez de se continuar a divisão, apa-
rece simplesmente uma outra estrutura. Para simplificar, vamos designar a
extremidade de um ramo por folha e iremos representá-la com um pequeno
círculo.
Para darmos dimensões à árvore, vamos considerar que a função arvore
recebe, como argumento, as coordenadas P da base da árvore, o compri-
mento c do tronco e o ângulo α do tronco. Para a fase recursiva, teremos
como parâmetros a diferença de ângulo de abertura ∆α que o novo tronco
124 CAPÍTULO 3. RECURSÃO
∆α ∆α
f ·c f ·c
c
α
arvore(p, c, a, da, f) =
let topo = p + vpol(c, a)
ramo(p, topo)
if c < 2
folha(topo)
else
arvore(topo, c*f, a + da, da, f)
arvore(topo, c*f, a - da, da, f)
end
end
ramo(p, topo) =
line(p, topo)
folha(topo) =
circle(topo, 0.2)
∆α0 ∆α1
f0 · c f1 · c
c
α
Estado
4.1 Introdução
Como vimos, a linguagem Julia permite-nos estabelecer uma associação
entre um nome e uma entidade através do operador =. Nesta secção, iremos
ver que o Julia permite-nos também usar esse operador para alterar essa
associação, fazendo com que o nome passe a estar associado a uma outra
entidade. Esta operação denomina-se atribuição.
Nesta secção vamos discutir mais em profundidade o conceito de atri-
buição. Para motivar vamos começar por introduzir um tópico importante
onde a atribuição tem um papel primordial: a aleatoriedade.
4.2 Aleatoriedade
Desenhar implica tomar decisões conscientes que conduzam a um objec-
tivo pretendido. Neste sentido, desenhar aparenta ser um processo racional
onde não há lugar para a aleatoriedade, a sorte ou a incerteza. De facto, nos
desenhos que temos feito até agora a racionalidade tem sido crucial, pois o
computador exige uma especificação rigorosa do que se pretende, não per-
mitindo quaisquer ambiguidades. No entanto, é sabido que um problema
de desenho frequentemente exige que o arquitecto experimente diferentes
soluções antes de encontrar aquela que o satisfaz. Essa experimentação
passa por empregar alguma aleatoriedade no processo de escolha dos “pa-
râmetros” do desenho. Assim, embora um desenho final possa apresentar
uma estrutura que espelha uma intenção racional do arquitecto, o processo
que conduziu a esse desenho final não é necessariamente racional e pode
ter passado por fases de ambiguidade e incerteza.
Quando a arquitectura se inspira na natureza surge um factor adicional
de aleatoriedade: em inúmeros casos, a natureza é intrinsecamente alea-
tória. Essa aleatoriedade, contudo, não é total e está sujeita a restrições.
Este facto é facilmente compreendido quando pensamos que, embora não
129
130 CAPÍTULO 4. ESTADO
proximo_aleatorio(ultimo_aleatorio) =
(25173*ultimo_aleatorio + 13849)%65536
julia> proximo_aleatorio(12345)
2822
julia> proximo_aleatorio(2822)
11031
julia> proximo_aleatorio(11031)
21180
4.3 Estado
A função aleatorio apresenta um comportamento diferente do das fun-
ções que vimos até agora. Até este momento, todas as funções que tínha-
mos definido comportavam-se como as funções matemáticas tradicionais:
dados argumentos, a função produz resultados e, mais importante, dados
os mesmos argumentos, a função produz sempre os mesmos resultados. Por
exemplo, independentemente do número de vezes que invocarmos a fun-
ção factorial, para um dado argumento ela irá sempre produzir o facto-
rial desse argumento.
A função aleatorio é diferente de todas as outras, pois, para além de
não precisar de argumentos, ela produz um resultado diferente de cada vez
que é invocada.
Do ponto de vista matemático, uma função sem parâmetros não tem
nada de anormal: é precisamente aquilo que se designa por uma constante.
De facto, tal como escrevemos sin cos x para designar sin(cos(x)), também
escrevemos sin π para designar sin(π()), onde se vê que π pode ser inter-
pretado como uma função sem argumentos.
Do ponto de vista matemático, uma função que produz resultados di-
ferentes a cada invocação não tem nada de normal: é uma aberração, pois,
de acordo com a definição matemática de função, esse comportamento não
é possível. E, no entanto, é precisamente este o comportamento que gosta-
ríamos de ter na função aleatorio.
Para se obter o pretendido comportamento é necessário introduzir o
conceito de estado. Dizemos que uma função tem estado quando o seu com-
portamento depende da sua história, i.e., das suas invocações anteriores. A
função aleatorio é um exemplo de uma função com estado, mas há inú-
meros outros exemplos no mundo real. Uma conta bancária também tem
134 CAPÍTULO 4. ESTADO
aleatorio() =
global numero_aleatorio = proximo_aleatorio(numero_aleatorio)
julia> aleatorio()
2822
julia> aleatorio()
11031
julia> numero_aleatorio
11031
Como se vê, de cada vez que a função aleatorio é invocada, o valor as-
sociado a numero_aleatorio é actualizado, permitindo assim influenciar
o futuro comportamento da função. Obviamente, em qualquer momento,
podemos re-iniciar a sequência de números aleatórios simplesmente re-
pondo o valor da semente:
julia> aleatorio()
21180
julia> aleatorio()
42629
julia> numero_aleatorio = 12345
julia> aleatorio()
2822
julia> aleatorio()
11031
julia> aleatorio()
21180
0123012301230123012301230123012301230123012301230123012301
proximo_aleatorio(ultimo_aleatorio) =
16807*ultimo_aleatorio % 2147483647
01001010001011110100100011000111100110110101101111000011110
21312020300221331333233301112313001012333020321123122330222
Para combinar estes dois requisitos, é usual que a função random receba
o limite superior de geração x e o analise para determinar se é inteiro ou
real, devolvendo um valor aleatório adequado em cada caso. Para isso, não
é preciso mais do que mapear o intervalo de geração de inteiros que, como
vimos, é [0, 2147483647[, no intervalo [0, x[. A implementação assenta na
possibilidade de, em Julia, se especializar uma função para diferentes tipos
de argumentos:
random(x) =
if x isa Int
random_number()%x
else
x*random_number()/2147483647.0
end
Exercício 4.4.1 As árvores produzidas pelas função arvore são pouco re-
alista pois são totalmente bidimensionais, com troncos que são simples li-
nhas e folhas que são pequenas circunferências. O cálculo das coordenadas
dos ramos e folhas é também bidimensional, assentando sobre coordena-
das polares que são dadas pelos parâmetros comprimento e angulo.
Pretende-se que redefina as funções ramo, folha e arvore de modo a
aumentar o realismo das árvores geradas.
Para simplificar, considere que as folhas podem ser simuladas por pe-
quenas esferas e que os ramos podem ser simulados por troncos de cone,
cujo raio da base é 10% do comprimento do tronco e cujo raio do topo é
90% do raio da base.
Para que a geração de árvores passe a ser verdadeiramente tridimen-
sional, redefina também a função arvore de modo a que o topo de cada
ramo seja um ponto em coordenadas esféricas escolhidas aleatoriamente
a partir da base do ramo. Isto implica que a função árvore, ao invés de
ter dois parâmetros para o comprimento e o ângulo das coordenadas pola-
res, precisará de ter três, para o comprimento, a longitude e a co-latitude.
Do mesmo modo, ao invés de receber os quatro limites para a geração de
comprimentos e ângulos aleatórios, precisará de seis limites para os três
parâmetros.
Experimente diferentes argumentos para a sua redefinição da função
arvore de modo a gerar imagens como a seguinte:
4.4. ESCOLHAS ALEATÓRIAS 141
Figura 4.3: Vista aérea da cidade de New York nos Estados Unidos. Foto-
grafia de Art Siegel.
P
l s
rua_predios(p, m_predios, l, h, s) =
if m_predios == 0
nothing
else
predio(p, l, h)
rua_predios(p + vx(l + s), m_predios - 1, l, h, s)
end
Figura 4.5: Uma urbanização em malha ortogonal com cem edifícios todos
da mesma altura.
predio(p, l, h) =
box(p, l, l, h)
predio(p, l, h) =
box(p, l, l, random_range(0.1, 1.0)*h)
Figura 4.6: Uma urbanização em malha ortogonal com cem edifícios com
alturas aleatórias.
Figura 4.7: Uma urbanização em malha ortogonal com cem edifícios com
alturas aleatórias.
148 CAPÍTULO 4. ESTADO
tal como anteriormente e a função predio1 deverá construir uma torre ci-
líndrica de altura aleatória e contida no espaço de um quarteirão. Defina
estas duas funções.
Exercício 4.5.2 Pretende-se que use as duas funções predio0 e predio1
definidas no exercício anterior para a redefinição da função predio de
modo a que esta, aleatoriamente, construa prédios diferentes. A urbani-
zação resultante deverá ser constituída aproximadamente por 20% de tor-
res circulares e 80% de prédios rectangulares, tal como é exemplificado na
imagem seguinte:
re
ri
p
rl
Estruturas
5.1 Introdução
A grande maioria das funções que definimos nas secções anteriores visa a
produção de formas geométricas concretas. Nesta secção, iremos abordar
funções cujo objectivo é a produção de formas geométricas abstractas, no
sentido de serem formas geométricas representadas apenas por um aglo-
merado de posições no espaço. Por exemplo, uma linha poligonal pode
ser representada apenas pela sequência de posições por onde a linha passa.
Essa sequência de posições pode, no entanto, ser usada para vários outros
fins como, por exemplo, para criar uma sequência de esferas com os cen-
tros nessas posições ou para definir a trajectória de um tubo que passa por
essas posições. Estes exemplos mostram que a manipulação destes aglo-
merados de posições é independentemente do uso subsequente que lhes
pretendemos dar.
Para podermos tratar aglomerados de posições como um todo é neces-
sário agrupar essas posições no que se designa por estruturas de dados. Estas
estruturas são arranjos particulares de dados que permitem tratá-los como
um todo. Uma lista telefónica, por exemplo, pode ser vista como uma estru-
tura de dados que organiza associações entre nomes e números de telefone.
Uma das estruturas de dados mais flexível que mais iremos utilizar
existe em muitas linguagens de programação e denomina-se lista.
5.2 Listas
Em termos genéricos, uma lista é apenas uma sequência de elementos. Di-
ferentes linguagens de programação implementam listas de diferentes ma-
neiras. Em Julia, as listas são um caso particular de uma estrutura de dados
mais sofisticada denominada array. Por uma questão de simplicidade, va-
mos discutir apenas este caso particular, que vamos continuar a denominar
por lista.
153
154 CAPÍTULO 5. ESTRUTURAS
• Que há um caso mais simples de todos onde existe uma resposta ime-
diata: quando a lista é vazia, a resposta é zero.
Experimentando, temos:
julia> numero_elementos([1, 2, 3, 4])
4
julia> numero_elementos([])
0
Exercício 5.3.4 Defina uma função inverte que recebe uma lista e de-
volve outra lista que possui os mesmos elementos da primeira só que por
ordem inversa.
5.3.1 Enumerações
Vamos agora considerar uma função capaz de criar enumerações. Dados
os limites a e b de um intervalo [a, b] e um incremento i, a função deverá
devolver uma lista com todos os números a, a+i, a+2i, a+3i, a+4i, . . . , a+
ni que não são superiores a b.
Mais uma vez, a recursão ajuda: a enumeração dos números no inter-
valo [a, b] com um incremento i é exactamente o mesmo que o número a
seguido da enumeração dos números no intervalo [a+i, b]. O caso mais sim-
ples de todos é uma enumeração num intervalo [a, b] em que a > b. Neste
caso o resultado é simplesmente uma lista vazia. Esta análise permite-nos
definir a função:
5.3. OPERAÇÕES COM LISTAS 157
enumera(a, b, i) =
if a > b
[]
else
[a, enumera(a+i, b, i)...]
end
julia> enumera(1, 5, 1)
[1, 2, 3, 4, 5]
julia> enumera(1, 5, 2)
[1, 3, 5]
enumera(a, b, i) =
begin
enumera_crescente(a) =
if a > b
[]
else
[a, enumera_crescente(a+i)...]
end
enumera_decrescente(a) =
if a < b
[]
else
[a, enumera_decrescente(a+i)...]
end
if i > 0
enumera_crescente(a)
else
enumera_decrescente(a)
end
end
julia> enumera(1, 5, 1)
[1, 2, 3, 4, 5]
julia> enumera(5, 1, -1)
[5, 4, 3, 2, 1]
julia> enumera(6, 0, -2)
[6, 4, 2, 0]
julia> iota(10, 1)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
julia> iota(10, 2)
[0, 2, 4, 6, 8]
Exercício 5.3.7 Escreva uma função elimina1 que recebe, como argumen-
tos, um número e uma lista e devolve, como resultado, outra lista onde a
primeira ocorrência desse número foi eliminada. Eis um exemplo:
Exercício 5.3.8 Escreva uma função elimina que recebe, como argumen-
tos, um número e uma lista e devolve, como resultado, outra lista onde esse
número não aparece. Eis um exemplo:
Exercício 5.3.9 Escreva uma função substitui que recebe dois números
e uma lista como argumentos e devolve outra lista com todas as ocorrên-
cias do segundo argumento substituídas pelo primeiro. Como exemplo,
considere:
5.4. POLÍGONOS 159
5.4 Polígonos
As listas são particularmente úteis para a representação de entidades ge-
ométricas abstractas, i.e., entidades para as quais apenas nos interessa sa-
ber algumas propriedades como, por exemplo, a sua posição. Para isso,
podemos usar uma lista de posições, i.e., uma lista cujos elementos são o
resultado de invocações dos construtores de coordenadas.
Imaginemos, a título de exemplo, que pretendemos representar um po-
lígono. Por definição, um polígono é uma figura plana limitada por um ca-
minho fechado composto por uma sequência de segmentos de recta. Cada
segmento de recta é uma aresta (ou lado) do polígono. Cada ponto onde se
encontram dois segmentos de recta é um vértice do polígono.
A partir da definição de polígono é fácil vermos que uma das formas
mais simples de representar um polígono será através da sequência de po-
sições que indica qual a ordem pela qual devemos unir os vértices com uma
aresta e onde se admite que o último elemento será unido ao primeiro. É
precisamente essa sequência de argumentos que fornecemos no seguinte
exemplo, onde usamos a função pré-definida polygon:
160 CAPÍTULO 5. ESTRUTURAS
tou as cinco chagas de Cristo crucificado para os Cristãos, foi associado às proporções do
corpo humano, foi usado como símbolo maçónico e, quando invertido, até foi associado ao
satanismo.
162 CAPÍTULO 5. ESTRUTURAS
2π r
5
vertices_pentagrama(p, raio) =
[p+vpol(raio, pi/2+0*4/5*pi),
p+vpol(raio, pi/2+1*4/5*pi),
p+vpol(raio, pi/2+2*4/5*pi),
p+vpol(raio, pi/2+3*4/5*pi),
p+vpol(raio, pi/2+4*4/5*pi)]
P2 P1 P2 P1
P3 P
P0 −→ 3 P0
P4 P4
P2 P1 P2 P1
P3 P
P0 −→ 3 P0
P4 P4
line(pontos)
spline(pontos)
Figura 5.7: Comparação entre uma linha poligonal e uma spline que unem
o mesmo conjunto de pontos.
siva para este problema podemos pensar que quando x0 > x1 o resultado
será uma lista vazia de coordenadas, caso contrário juntamos a coordenada
(x0 , sin(x0 )) à lista de coordenadas do seno para o intervalo [x0 + ∆x , x1 ].
Esta definição é traduzida directamente para a seguinte função:
pontos_seno(x0, x1, dx) =
if x0 > x1
[]
else
[xy(x0, sin(x0)), pontos_seno(x0+dx, x1, dx)...]
end
5.6 Treliças
Uma treliça é uma estrutura composta por barras rígidas que se unem em
nós, formando unidades triangulares. Sendo o triângulo o único polígono
5.6. TRELIÇAS 171
Figura 5.13: Treliça cujos elementos triangulares não são idênticos entre si.
b0 b1
a0 a1 a2
c0 c1 c2
nos_trelica(ps) =
for p in ps
no_trelica(p)
end
barras_trelica(ps, qs) =
if ps == [] || qs == []
nothing
else
barra_trelica(ps[1], qs[1])
barras_trelica(ps[2:end], qs[2:end])
end
barras_trelica(ps, qs) =
for (p, q) in zip(ps, qs)
barra_trelica(p, q)
end
Uma vez que é necessário percorrer duas listas, o operador for neces-
sita de duas variáveis, neste caso, p e q, variáveis essas que irão ser suces-
sivamente atribuídas com os elementos correspondentes das duas listas,
sendo a função zip quem assegura essa correspondência.3
Para interligar cada nó ai ao correspondente nó ci , apenas temos de
avaliar barras_trelica(ais, cis). O mesmo poderemos dizer para in-
terligar cada nó bi ao nó ai correspondente e para interligar cada bi a cada
ci . Assim, temos:
trelica(ais, bis, cis) =
begin
nos_trelica(ais)
nos_trelica(bis)
nos_trelica(cis)
barras_trelica(ais, cis)
barras_trelica(bis, ais)
barras_trelica(bis, cis)
...
end
Para ligar os nós bi aos nós ai+1 podemos simplesmente subtrair o pri-
meiro nó da lista ais e estabelecer a ligação como anteriormente. O mesmo
podemos fazer para ligar cada bi a cada ci+1 . Finalmente, para ligar cada
ai a cada ai+1 podemos usar a mesma ideia mas aplicando-a apenas à lista
ais. O mesmo podemos fazer para a lista cis. A função completa fica,
então:
trelica(ais, bis, cis) =
begin
nos_trelica(ais)
nos_trelica(bis)
nos_trelica(cis)
barras_trelica(ais, cis)
barras_trelica(bis, ais)
barras_trelica(bis, cis)
barras_trelica(bis, ais[2:end])
barras_trelica(bis, cis[2:end])
barras_trelica(ais[2:end], ais)
barras_trelica(cis[2:end], cis)
barras_trelica(bis[2:end], bis)
end
raio_no_trelica = 0.1
no_trelica(p) =
sphere(p, raio_no_trelica)
raio_barra_trelica = 0.03
barra_trelica(p0, p1) =
cylinder(p0, raio_barra_trelica, p1)
bi
l
h
ai
ai+1
ci
ci+1
l l
b0 b1 ... bn−1
a0 a1 ... an−1 an
Defina uma função trelica_plana que recebe, como parâmetros, duas lis-
tas correspondentes às posições desde a0 até an e desde b0 até bn−1 e que
cria os nós nessas posições e as barras que os unem. Considere, como pré-
definidas, as funções nos_trelica que recebe uma lista de posições como
argumento e barras_trelica que recebe duas listas de posições como ar-
gumentos.
Teste a função de definir com a seguinte expressão:
c0
a0
c1
a1
cn
an
b0
bn−1
ψ0 ψ1 ∆ψ
∆ψ
2
r1
r0
P x
decisão leva-nos a considerar que os ângulos inicial e final dos arcos devem
ser medidos relativamente ao eixo Z, tal como é visível na Figura 5.16. Para
flexibilizar a produção das coordenadas dos nós do arco vamos definir uma
função que recebe o centro P do arco, o raio r desse arco, o ângulo φ, o
ângulo inicial ψ, o incremento de ângulo ∆ψ e, finalmente, o número de
posições a produzir. Assim, temos:
o arranque de cada treliça são tais que os nós dos topos das treliças são
coincidentes dois a dois e estão dispostos ao longo de um círculo de raio r,
tal como se apresenta no esquema seguinte:
ψ0
r0
r
e
x
α
Repare que a escada de mão pode ser vista como uma versão (muito)
simplificada de uma treliça composta por apenas duas sequências de nós.
Defina a função escada_de_mao que recebe, como parâmetros, duas listas
de pontos correspondentes aos centros das sequências de nós e que cria os
nós nesses pontos e as barras que os unem. Considere, como pré-definidas,
as funções nos_trelica que recebe uma lista de posições como argumento
e barras_trelica que recebe duas listas de posições como argumentos.
Exercício 5.6.7 Defina uma função capaz de representar a dupla hélice do
genoma, tal como se pode ver na seguinte imagem:
184 CAPÍTULO 5. ESTRUTURAS
Figura 5.18: Treliças espaciais no estádio Al Ain nos Emiratos Árabes Uni-
dos. Fotografia de Klaus Knebel.
de pontos, i.e., por um número ímpar de listas de pontos (no mínimo, três
listas).
A definição da função que contrói uma treliça espacial segue a mesma
lógica da função que constrói uma treliça simples só que agora, em vez de
operar com apenas três listas, opera com um número impar delas. Assim,
a partir de uma lista contendo um número ímpar de listas de coordenadas,
iremos processar essas listas de coordenadas duas a duas, sabendo que a
“terceira” lista de coordenadas ci,j da treliça i é também a “primeira” lista
de coordenadas ai+1,j da treliça seguinte i + 1.
b0,0 b0,1
a0,0 a0,1 a0,2
b1,0 b1,1
c0,0 = a1,0 c0,2 = a1,2
c0,1 = a1,1
trelica_espacial(ptss) =
let ais = ptss[1],
bis = ptss[2],
cis = ptss[3]
nos_trelica(ais)
nos_trelica(bis)
barras_trelica(ais, cis)
barras_trelica(bis, ais)
barras_trelica(bis, cis)
barras_trelica(bis, ais[2:end])
barras_trelica(bis, cis[2:end])
barras_trelica(ais[2:end], ais)
barras_trelica(bis[2:end], bis)
if ptss[4:end] == []
nos_trelica(cis)
barras_trelica(cis[2:end], cis)
else
barras_trelica(bis, ptss[4])
trelica_espacial(ptss[3:end])
end
end
coordenadas_x(p, l, n) =
[p+vx(i*l) for i in 0:n-1]
Figura 5.20: Uma treliça espacial horizontal, composta por oito treliças sim-
ples com dez pirâmides cada uma.
Esta treliça é semelhante à treliça espacial em arco mas com uma nuance:
os raios exterior rac e interior rb variam ao longo do eixo do arco. Esta
variação corresponde a uma sinusoide de amplitude ∆r a variar desde um
valor inicial α0 até um valor final α1 , em incrementos de ∆α .
Defina a função trelica_ondulada que constrói este tipo de treliças, a
partir dos mesmos parâmetros da função trelica_espacial_arco e ainda
dos parâmetros α0 , α1 , ∆α e ∆r . Como exemplo, considere que a figura
anterior foi gerada pela invocação seguinte:
Formas
6.1 Introdução
Até agora temos lidado apenas com curvas e sólidos simples. Nesta secção
vamos discutir formas mais complexas criadas a partir da união, intersec-
ção e subtracção de formas mais simples. Como iremos ver, estas operações
são muito utilizadas em arquitectura.
A Figura 6.1 ilustra o templo de Portuna (também conhecido, errada-
mente, por templo de Fortuna Virilis), uma construção do século um antes
de Cristo caracterizada por usar colunas, não como elemento eminente-
mente estrutural, mas sim como elemento decorativo. Para isso, os arqui-
tectos consideraram uma união entre uma parede estrutural e um conjunto
de colunas, de modo a que as colunas ficassem embebidas na parede. Esta
mesma abordagem é visível noutros monumentos como, por exemplo, o
Coliseu de Roma.
Já no caso do edifício visivel na Figura 6.2, da autoria do atelier de Mi-
chel Rojkind, o arquitecto criou uma forma inovadora através da subtracção
de esferas a formas paralelipipédicas.
A Figura 6.3 demonstra um terceiro exemplo. Neste caso, trata-se da
Catedral da Sagrada Família, do arquitecto Catalão Antoni Gaudí. Como
iremos ver na secção 6.7, algumas das colunas idealizadas por Gaudí para
esta obra resultam da intersecção de prismas torcidos.
191
192 CAPÍTULO 6. FORMAS
e pelas expressões:
cubo = box(xyz(6, 0, 0), xyz(7, 1, 1))
esfera = sphere(xyz(6, 1, 1), 0.5)
subtraction(esfera, cubo)
Este objecto tem a propriedade de, quando visto em planta, alçado late-
ral e alçado frontal, se reduzir a um quadrado. No lado direito da mesma
figura encontra-se um sólido ainda mais interessante, gerado pela intersec-
ção dos mesmos três cilindros, produzindo um objecto que, obviamente,
não é uma esfera mas que, tal como a esfera, projecta um círculo em planta,
em alçado frontal e em alçado lateral.
Para melhor percebermos a diferença entre o objecto construído pela in-
tersecção dos cilindros e uma verdadeira esfera, podemos subtrair a esfera
6.2. GEOMETRIA CONSTRUTIVA 195
e e
r P r P
r P
Defina uma função denominada bacia que constroi uma bacia idêntica
à da figura anterior.
Exercício 6.2.2 Pretende-se modelar uma banheira de pedra idêntica à
que se apresenta na imagem seguinte:
6.3. SUPERFÍCIES 197
e l e
r P r r P
r P r
6.3 Superfícies
Até agora, temos usado o Khepri para a criação de curvas, que visualiza-
mos normalmente em duas dimensões, por exemplo, no plano XY , ou para
a criação de sólidos, que visualizamos a três dimensões. Agora, vamos ver
que o Khepri também permite a criação de superfícies.
198 CAPÍTULO 6. FORMAS
surface_circle(...)≡surface(circle(...))
surface_rectangle(...)≡surface(rectangle(...))
surface_polygon(...)≡surface(polygon(...))
ri rf
α
ρ
re
rf = ρ sin α
ri = ρ cos α
ρ + rf = re
Se assumirmos que o parâmetro fundamental é o raio exterior do trifólio,
re , então podemos deduzir que:
re
ρ=
1 + sin πn
re
rf =
1 + sin1 π
n
6.3. SUPERFÍCIES 201
cos πn
ri = re
1 + sin πn
A partir destas equações, podemos decompor o processo de criação de
um n-fólio como a união de uma sucessão de círculos de raio rf dispostos
circularmente com um último círculo central de raio ri . Transcrevendo para
Julia, temos:
n_folio(p, re, n) =
union(folhas_folio(p, re, n), circulo_interior_folio(p, re, n))
circulo_interior_folio(p, re, n) =
surface_circle(p, re*cos(pi/n)/(1+sin(pi/n)))
union(c1 ,
union(c2 ,
...
union(cn ,
???)))
Torna-se agora claro que ??? tem de ser algo que possa ser usado como
argumento de uma união e que, para além disso, não afecta as uniões pen-
dentes. Ora isto implica que ??? tem de ser o elemento neutro da união
202 CAPÍTULO 6. FORMAS
folhas_folio(p, re, n) =
uniao_circulos(p, re/(1+sin(pi/n)),
0,
2*pi/n,
re/(1+1/sin(pi/n)),
n)
n_folio(xy(0, 0), 1, 3)
n_folio(xy(2.5, 0), 1, 4)
6.3. SUPERFÍCIES 203
Figura 6.11: Fólios com número de folhas crescente. De cima para baixo e
da esquerda para a direita estão representados um pentafólio, um hexafó-
lio, um heptafólio, um octofólio, um eneafólio e um decafólio.
circulo_interior_folio(p, re, n) =
if n == 2
empty_shape()
else
surface_circle(p, re*cos(pi/n)/(1+sin(pi/n)))
end
soma(numeros) =
if numeros == []
0
else
numeros[1]+soma(numeros[2:end])
end
Como podemos ver na função anterior, a soma de uma lista vazia de nú-
meros é zero. Isto faz sentido pois, se não há números para somar, o total é
necessariamente zero. Por outro lado, quando pensamos que, durante a re-
cursão ficam somas pendentes então, quando se atinge o caso de paragem,
é necessário que seja devolvido um valor que não afecte as somas penden-
tes e, mais uma vez, faz sentido que esse valor tenha de ser zero pois esse é
o elemento neutro da soma.
Do mesmo modo, se pensarmos numa função que calcule a união de
uma lista de regiões, devemos escrever:
unioes(regioes) =
if regioes == []
empty_shape()
else
union(regioes[1], unioes(regioes[2:end]))
end
Embora a maior parte das pessoas tenha a tentação de usar para valor
do caso básico o número zero, isso seria incorrecto pois esse zero, por ser
o elemento absorvente do produto, seria propagado ao longo da sequên-
cia de produtos que ficaram pendentes até ao caso básico, produzindo um
resultado final de zero. Assim, torna-se claro que o valor correcto do caso
básico tem de ser 1, i.e., o elemento neutro do produto:
produto(numeros) =
if numeros == []
1
else
numeros[1]*produto(numeros[2:end])
end
interseccoes(regioes) =
if regioes == []
???
else
intersection(regioes[1], interseccoes(regioes[2:end]))
end
já não é tão evidente saber o que colocar no lugar de ???. O que sabemos
é que esse valor tem de ser o elemento neutro da operação de combina-
ção que, neste caso, é a intersecção. Felizmente, não é difícil concluir que
esse elemento neutro é, necessariamente, a forma universal U , i.e., a forma
que inclui todos os pontos do espaço. Essa forma é produzida pela função
universal_shape. Assim, já podemos definir:
interseccoes(regioes) =
if regioes == []
universal_shape()
else
intersection(regioes[1], interseccoes(regioes[2:end]))
end
R∪∅=∅∪R=R
R∪U =U ∪R=U
R∪R=R
R∩∅=∅∩R=∅
R∩U =U ∩R=R
R∩R=R
R\∅=R
∅\R=∅
R\R=∅
Exercício 6.4.1 A álgebra de regiões que elaborámos encontra-se incom-
pleta por não incluir a operação de complemento de uma região. O comple-
mento RC de uma região R define-se como a subtracção à região universal
U da região R:
RC = U \ R
6.4. ÁLGEBRA DE FORMAS 207
regulares é feita de modo a que cada vértice seja o centro de um novo polí-
gono idêntico, mas inscrito num círculo cujo raio é uma fracção (dada por
αr ) do raio r, sendo este processo repetido um determinado número de ní-
veis. Por exemplo, na figura anterior, a imagem mais à esquerda foi feita
com p = (0, 0), r = 1, φ = 0, αr = 0.3, n = 4 e com um número de níveis
igual a 2. No conjunto, as imagens foram produzidas pela avaliação das
seguintes expressões:
Note que todos os cones têm o seu vértice no mesmo ponto e estão
orientados de modo a que o centro da base fica assente numa esfera virtual.
Note que todos os “meridianos” e “paralelos” possuem o mesmo número
de cones e note também que o raio da base dos cones diminui à medida que
nos aproximamos dos “pólos” para que os cones não interfiram uns com os
outros.
Escreva um programa em Julia capaz de construir a “esfera” de cones.
Exercício 6.4.11 Considere uma variação sobre a “esfera” de cones do
exercício anterior em que, ao invés de diminuirmos o raio da base dos co-
nes à medida que nos aproximamos dos pólos, diminuímos o número de
cones, tal como se apresenta na seguinte imagem:
Exercício 6.4.12 Defina uma função em Julia capaz de criar cascas esféri-
cas perfuradas, tal como as que se apresentam em seguida:
Para modelar a imagem anterior, defina uma função que recebe dois
pontos que definem os limites de um volume imaginário (paralelo aos ei-
xos coordenados) dentro do qual estão localizados os centros das esferas
e ainda o valor mínimo e máximo do raio de cada esfera, a espessura da
casca, o número de esferas a criar e, finalmente, os parâmetros necessários
para realizar o mesmo tipo de perfurações em cada esfera.
Note que o interior da construção deverá estar desimpedido, i.e., tal
como se ilustra no seguinte corte:
214 CAPÍTULO 6. FORMAS
~
N
gomo_esfera(p, r, fi) =
slice(slice(sphere(p, r),
p,
vcyl(1, 0, 0)),
p,
vcyl(1, fi, 0))
u0() =
xyz(0, 0, 0)
vux() =
vxyz(1, 0, 0)
vuy() =
vxyz(0, 1, 0)
vuz() =
vxyz(0, 0, 1)
slice(slice(slice(sphere(u0(), 2),
u0(), vux()),
u0(), vuy()),
u0(), vuz())
1
Johannes Kepler foi um famoso matemático e astrónomo que, entre muitas outras con-
tribuições, estableceu as leis do movimento planetário.
220 CAPÍTULO 6. FORMAS
P1
d
P0
6.6 Extrusões
Vimos nas secções anteriores que o Khepri disponibiliza um conjunto de só-
lidos pré-definidos tais como esferas, paralelipípedos, pirâmides, etc. Atra-
vés da composição desses sólidos é possível modelar formas bastante mais
2
Wacław Sierpiński foi um matemático polaco que deu enormes contribuições à teoria
dos conjuntos e à topologia. Sierpiński descreveu uma versão bidimensional desta pirâ-
mida em 1915.
6.6. EXTRUSÕES 221
y(x) = a sin(ωx + φ)
a
x
φ
Note-se que os pontos gerados estão todos assentes num plano paralelo
ao plano XY , com a sinusóide a evoluir numa direcção paralela ao eixo X,
a partir de um ponto p. A Figura 6.23 mostra três dessas curvas obtidas a
partir das seguintes expressões:
Figura 6.24: Uma parede sinusoidal produzida por extrusão simples a par-
tir de uma região formada por duas sinusóides unidas nas pontas de forma
a fazerem uma curva fechada.
Para construir uma parede sinusoidal basta-nos agora criar duas sinu-
sóides afastadas de uma determinada distância igual à espessura e da pa-
rede, curvas essas que iremos fechar para formar uma região que, depois,
extrudimos para ficar com uma dada altura h:
Cada uma das curvas é uma spline produzida a partir de pontos gerados
pela função pontos_circulo_raio_aleatorio definida no exercício 5.5.2.
Escreva uma expressão capaz de gerar um sólido semelhante aos apresen-
tados na imagem anterior.
Exercício 6.6.2 A Figura 6.25 apresenta um detalhe da fachada do Hotel
Marriott em Anaheim. É fácil vermos que as varandas da fachada do hotel
são sinusóides de igual amplitude e frequência mas que, entre cada dois pi-
sos, as sinusóides apresentam fases opostas. Para além disso, cada varanda
corresponde a uma sinusóide localizada a uma determina cota z.
Crie uma função denominada laje que, recebendo todos os parâme-
tros necessários para a geração de uma sinusóide, cria uma laje rectangular
mas com a fachada de forma sinusoidal, tal como se apresenta na seguinte
imagem:
228 CAPÍTULO 6. FORMAS
ly
a
x
p
φ lx
Exercício 6.6.4 Crie uma função denominada prumos que, recebendo to-
dos os parâmetros necessários para a geração de uma sinusóide, cria os
prumos de apoio a um corrimão de curva sinusoidal, tal como se apresenta
na seguinte imagem:
Exercício 6.6.5 Crie uma função denominada guarda que, recebendo to-
dos os parâmetros necessários para a criação do corrimão e dos prumos,
cria uma guarda, tal como se apresenta na imagem seguinte:
230 CAPÍTULO 6. FORMAS
Exercício 6.6.6 Crie uma função denominada piso que, recebendo todos
os parâmetros necessários para a criação da laje e da guarda, cria um piso,
tal como se apresenta na imagem seguinte:
piso(xy(0, 0), 1.0, 1.0, 0, 2*pi, 0.5, 2, 0.2, 1, 0.04, 0.02, 0.4)
Exercício 6.6.7 Crie uma função denominada predio que recebe todos os
parâmetros necessários para a criação de um andar, incluindo a altura de
cada andar a_andar e o número de andares n_andares, e cria um prédio
com esses andares, tal como se apresenta na imagem seguinte:
predio(xy(0, 0), 1.0, 1.0, 0, 20*pi, 0.5, 20, 0.2, 1, 0.04, 0.02, 0.4, 4, 8)
uma direcção fixa pois o resultado não teria a forma sinusoidal pretendida.
Para resolvermos este problema, ao invés de extrudirmos o círculo segundo
uma recta, podemos simplesmente fazer deslocar esse círculo ao longo de
uma sinusóide, obtendo a imagem da Figura 6.26. Esta imagem foi produ-
zida pelas expressões:
let seccao = surface_circle(xy(0, 0), 0.4),
curva = spline(pontos_sinusoide(xy(0, 0), 0.2, 1, 0, 0, 7*pi, 0.2))
sweep(curva, seccao)
end
Exercício 6.6.10 Refaça o exercício 6.6.3 mas usando uma extrusão ao longo
de um caminho.
Exercício 6.6.11 Defina uma função parede_pontos que, dada a espes-
sura e altura de uma parede e dada uma curva descrita por uma lista de
pontos, constrói uma parede através do deslocamento de uma secção rec-
tangular com a espessura e altura dadas ao longo de uma spline que passa
pelas coordenadas dadas. Se precisar, pode usar a função spline que, a
partir de uma lista de pontos, constrói uma spline que passa por esses pon-
tos.
6.7. COLUNAS DE GAUDÍ 235
intersection(
prisma_torcido(xy(0, 0), 1, 4, 10, 0, +pi/8, 0.9),
prisma_torcido(xy(0, 0), 1, 4, 10, 0, -pi/8, 0.9))
union(
prisma_torcido(xy(5, 0), 1, 4, 10, 0, +pi/8, 0.9),
prisma_torcido(xy(5, 0), 1, 4, 10, 0, -pi/8, 0.9))
intersection(
prisma_torcido(xy(10, 0), 1, 4, 10, 0, +pi/16, 0.9),
prisma_torcido(xy(10, 0), 1, 4, 10, 0, -pi/16, 0.9),
prisma_torcido(xy(10, 0), 1, 4, 10, pi/4, +pi/16, 0.9),
prisma_torcido(xy(10, 0), 1, 4, 10, pi/4, -pi/16, 0.9))
240 CAPÍTULO 6. FORMAS
union(
prisma_torcido(xy(15, 0), 1, 4, 10, 0, +pi/16, 0.9),
prisma_torcido(xy(15, 0), 1, 4, 10, 0, -pi/16, 0.9),
prisma_torcido(xy(15, 0), 1, 4, 10, pi/4, +pi/16, 0.9),
prisma_torcido(xy(15, 0), 1, 4, 10, pi/4, -pi/16, 0.9))
6.8 Revoluções
Uma superfície de revolução é uma superfície gerada pela rotação de uma
curva bi-dimensional em torno de um eixo. Um sólido de revolução é um
sólido gerado pela rotação de uma região bidimensional em torno de um
eixo.
Sendo a rotação um processo muito simples de criação de superfícies e
sólidos, é natural que o Khepri disponibilize uma operação para o fazer. A
função revolve destina-se precisamente a esse fim. A função revolve re-
cebe, como argumentos, a região a “revolver” e, opcionalmente, um ponto
no eixo de rotação (por omissão, a origem), um vector paralelo ao eixo de
rotação (por omissão, o vector na direcção do eixo Z), o ângulo inicial de
revolução (por omissão, zero) e o incremento de ângulo para a revolução
(por omissão, 2 · π). Naturalmente, se se omitir o incremento ou este for de
2 · π radianos, obtém-se uma revolução completa.
Figura 6.33: Uma spline usada como base para a construção de uma super-
fície de revolução.
cupula_revolucao(pontos) =
revolve(spline(pontos), pontos[1])
cupula_revolucao([xyz(0, 0, 12),
xyz(0, 1, 8),
xyz(0, 6, 4),
xyz(0, 6, 2),
xyz(0, 5, 0)])
cupula_revolucao([xyz(15, 0, 9),
xyz(15, 3, 8),
xyz(15, 7, 5),
xyz(15, 8, 3),
xyz(15, 8, 2),
xyz(15, 7, 0)])
cupula_revolucao([xyz(30, 0, 13),
xyz(30, 1, 10),
xyz(30, 5, 7),
xyz(30, 5, 2),
xyz(30, 3, 0)])
a
x
φ
lx
r1
r2 r2
α α
P
r0
Defina uma função ovo que constrói um ovo. A função deverá receber,
apenas, as coordenadas do ponto P , os raios r0 e r1 e, finalmente, a altura
h do ovo.
Para construirmos a arcada, o mais simples será definir uma função que
constrói uma coluna e um arco e que, recursivamente, constrói as restantes
arcadas até não ter mais arcadas para construir, caso em que constrói a
última coluna que suporta o último arco. A tradução deste algoritmo para
Julia é:
z r0
e
e
e
r1 h
e e
P r0
x
Cada uma das curvas é uma spline produzida a partir de pontos gerados
pela função pontos_circulo_raio_aleatorio definida no exercício 5.5.2.
Escreva uma expressão capaz de gerar um sólido semelhante aos apresen-
tados na imagem anterior.
Capítulo 7
Transformações
7.1 Introdução
Até agora, todos os objectos que construímos possuem um carácter defi-
nitivo: os parâmetros usados para os construir determinam univocamente
a forma desses objectos e tudo o que podemos fazer é invocar as funções
que constroem esses objectos com diferentes parâmetros para construirmos
objectos diferentes.
Embora essa forma de construção de objectos seja suficientemente po-
derosa, existem alternativas potencialmente mais práticas que se baseiam
na modificação de objectos previamente criados. Essa modificação é reali-
zada através de operações de translação, rotação, reflexão e escala.
É importante salientarmos que estas operações não criam novos objec-
tos, apenas afectam aqueles a que se aplicam. Por exemplo, após a aplica-
ção de uma translação a um objecto, este objecto muda simplesmente de
posição.
Contudo, por vezes queremos aplicar transformações a objectos que
produzam novos objectos como resultado. Por exemplo, podemos querer
produzir a translação de um objecto, mas deixando o objecto original no
lugar onde estava. Uma forma de o conseguirmos será aplicar a operação
de translação, não ao objecto original, mas sim a uma sua cópia. A possi-
bilidade de criarmos cópias dos objectos pode então ser usada para distin-
guirmos o caso em que queremos que uma operação modifique um objecto
do caso em que queremos que uma operação crie um novo objecto. Para
este propósito, o Khepri disponibiliza a operação copy_shape que recebe
um objecto como argumento e devolve uma cópia desse objecto, localizada
exactamente no mesmo local do original. Naturalmente, na ferramenta de
CAD irão aparecer dois objectos iguais sobrepostos. Tipicamente, o ob-
jecto que foi copiado será subsequentemente transformado, por exemplo,
movendo-o para outro local.
De seguida iremos ver quais são as operações de transformação dispo-
255
256 CAPÍTULO 7. TRANSFORMAÇÕES
níveis em Khepri.
7.2 Translação
cruz_papal(p, raio) =
union(cylinder(p,
raio,
p+vz(20*raio)),
cylinder(p+vxz(-7*raio, 9*raio),
raio,
p+vxz(7*raio, 9*raio)),
cylinder(p+vxz(-5*raio, 13*raio),
raio,
p+vxz(5*raio, 13*raio)),
cylinder(p+vxz(-3*raio, 17*raio),
raio,
p+vxz(3*raio, 17*raio)))
cruz_papal(raio) =
union(cylinder(u0(),
raio,
z(20*raio)),
cylinder(xz(-7*raio, 9*raio),
raio,
xz(7*raio, 9*raio)),
cylinder(xz(-5*raio, 13*raio),
raio,
xz(5*raio, 13*raio)),
cylinder(xz(-3*raio, 17*raio),
raio,
xz(3*raio, 17*raio)))
7.3 Escala
A escala consiste numa transformação que aumenta ou diminui a dimensão
de uma entidade sem lhe modificar a forma. Esta operação é também de-
nominada de homotetia. Embora seja concebível ter uma operação de escala
que modifica independentemente cada uma das dimensões, é mais usual
empregar-se uma escala uniforme que modifica simultaneamente as três
dimensões, afectando-as de um mesmo factor. Se o factor é maior que um,
o tamanho aumenta. Se o factor é menor que um, o tamanho diminui.
No caso do Khepri, apenas é disponibilizada uma operação de escala
uniforme: scale. É fácil vermos que a escala, para além de alterar a di-
mensão do objecto, pode alterar também a sua posição. Se esse efeito não
for pretendido, a solução óbvia é aplicar previamente uma translação para
centrar o objecto na origem, aplicar de seguida a operação de escala e, final-
mente, aplicar a translação inversa para “devolver” o objecto à sua posição
original.
Usando a operação de escala, é possível simplificar ainda mais a defi-
nição anterior. Na verdade, como a dimensão da cruz depende apenas do
raio, podemos arbitrar um raio unitário que depois alteramos através de
uma operação de escala. Assim, podemos escrever:
cruz_papal() =
union(cylinder(u0(), 1, 20),
cylinder(xz(-7, 9), 1, xz(7, 9)),
cylinder(xz(-5, 13), 1, xz(5, 13)),
cylinder(xz(-3, 17), 1, xz(3, 17)))
Figura 7.2: Da esquerda para a direita, uma cruz papal de raio unitário
colocada na origem, seguida de uma translação, seguida de uma escala
com translação, seguida de escala com rotação e translação e, finalmente,
escala com rotação em torno do eixo X e translação.
scale(cruz_papal(), 3)
7.4 Rotação
Na rotação, todos os pontos de um objecto giram num movimento circular
em torno de um ponto (a duas dimensões) ou eixo (a três dimensões). No
caso geral de uma rotação a três dimensões é usual decompor-se esta em
três rotações sucessivas em torno dos eixos coordenados. Estas rotações em
torno dos eixos X, Y e Z designam-se rotações principais, por analogia com
o conceito de eixos principais que se aplica a X, Y e Z. Cada uma destas
rotações é realizada pela função rotate que recebe, como argumentos, o
objecto sobre o qual se aplica a rotação, o ângulo de rotação, e dois pontos
que definem o eixo de rotação. Por omissão, esses pontos são a origem e
um ponto acima do anterior, o que implica que, por omissão, a rotação será
feita em relação ao eixo Z.
A Figura 7.2 ilustra algumas combinações de translações, escalas e ro-
tações, tendo sido gerada a partir do seguinte programa:
cruz_papal()
move(cruz_papal(), vx(20))
move(scale(cruz_papal(), 1.25), vx(40))
move(rotate(scale(cruz_papal(), 1.5), pi/4), vx(60))
move(rotate(scale(cruz_papal(), 1.75), pi/4, u0(), vx()), vx(80))
7.5 Reflexão
Para além da translação, escala, e rotação, o Khepri implementa ainda a
operação de reflexão, disponibilizando, para isso, a função mirror. Esta
função recebe, como argumentos, a forma a reflectir e um plano de reflexão
260 CAPÍTULO 7. TRANSFORMAÇÕES
Figura 7.5: Placa comemorativa que explica a idéia de Utzon para a mo-
delação das conchas. Fotografia de Matt Prebble, Reino Unido, Dezembro
2006.
nal, de Utzon. Esta idéia de Utzon está explicada num modelo de bronze
colocado junto ao edifício da Ópera, tal como se pode ver na Figura 7.5.
Infelizmente, os atrasos na construção, bem como os custos elevados
que se estavam a acumular, levaram o governo a questionar a decisão po-
lítica de construir a Ópera e forçaram Utzon a demitir-se quando ainda
faltava a construção dos interiores. Utzon ficou destroçado e abandonou a
Austrália em 1966 para nunca mais voltar. Contra a vontade da maioria dos
arquitectos, a sua obra-prima foi completada por Peter Hall e inaugurada
em 1973 sem que se fizesse uma única referência a Utzon. Infelizmente, o
trabalho de Peter Hall não esteve ao mesmo nível do de Utzon e o contraste
entre os magníficos exteriores e os banais interiores levou a que a obra fosse
considerada uma “semi-obra-prima.”1
Nesta secção, vamos modelar as conchas da Ópera de Sydney seguindo
exactamente a mesma solução proposta por Utzon. Todas as conchas do
edifício serão modeladas por triângulos esféricos obtidos através de três
cortes numa esfera. A Figura 7.6 mostra duas esferas de igual raio onde
1
Apesar do drama pessoal de Utzon, esta história acabou por ter um final feliz: trinta
anos mais tarde, o governo Australiano remodelou a Ópera de Sydney para a transformar
na verdadeira obra-prima que ela merecia ser e conseguiu que Utzon, que nunca chegou a
ver a sua obra acabada, aceitasse dirigir os trabalhos que visavam devolver-lhe o aspecto
original.
7.6. A ÓPERA DE SYDNEY 263
Figura 7.6: Duas das meia-conchas que constituem a Ópera de Sydney so-
brepostas às esferas de onde foram obtidas por uma sucessão de cortes.
Figura 7.7: Vista de topo de duas das meia-conchas que constituem a Ópera
de Sydney sobrepostas às esferas de onde foram obtidas por uma sucessão
de cortes.
Para fazer uma concha completa temos apenas de aplicar uma reflexão
à meia-concha, segundo o plano de corte vertical. Para juntar as duas par-
tes, temos de fazer uma união, o que sugere a criação de uma função que
combine a união com a reflexão:2
union_mirror(s, p, v) =
union(s, mirror(s, p, v))
2
Na realidade, esta operação já existe pré-definida em Khepri.
266 CAPÍTULO 7. TRANSFORMAÇÕES
conchas_sydney() =
union([concha(xyz(-45.01, 0.0, 0.0), 75, 2,
vsph(1, 1.9701, 4.5693), vsph(1, 1.9125, 2.5569)),
concha(xyz(-38.92, -13.41, -18.85), 75, 2,
vsph(1, 1.9314, 4.5902), vsph(1, 1.7495, 1.9984)),
concha(xyz(-38.69, -23.04, -29.89), 75, 2,
vsph(1, 1.9324, 4.3982), vsph(1, 1.5177, 1.9373)),
concha(xyz(-58.16, 81.63, -14.32), 75, 2,
vsph(1, 1.6921, 3.9828), sph(1, 1.4156, 1.9618)),
concha(xyz(-32.0, 73.0, -5.0), 75, 2,
vsph(1, 0.91, 4.1888), vsph(1, 0.8727, 1.3439)),
concha(xyz(-33.0, 44.0, -20.0), 75, 2,
vsph(1, 1.27, 4.1015), vsph(1, 1.1554, 1.2217))])
opera_sydney() =
begin
meia_opera_sydney(0.1964, 1.0, 43)
meia_opera_sydney(-0.1964, 0.8, -15)
end
y
ri
re
Exercício 7.6.2 Defina uma função capaz de criar correntes como as que se
apresentam em seguida:
7.6. A ÓPERA DE SYDNEY 269
Note que, à medida que se vai construindo a corrente, os elos vão so-
frendo sucessivas rotações em torno do eixo X.
Exercício 7.6.3 Defina uma função capaz de criar correntes fechadas como
a que se apresenta em seguida:
Note que, à medida que se vai construindo a corrente, os elos vão so-
frendo sucessivas rotações em torno do eixo X.
270 CAPÍTULO 7. TRANSFORMAÇÕES
Capítulo 8
Ordem Superior
8.1 Introdução
Temos demonstrado como a linguagem Julia nos permite, através da defini-
ção de funções apropriadas, gerarmos as formas arquitecturais que temos
em mente. Uma das vantagens da definição dessas funções é que mui-
tas dessas formas arquitecturais ficam parametrizadas, permitindo-nos fa-
cilmente experimentar variações até atingirmos os valores numéricos dos
parâmetros que nos satisfazem.
Apesar da “infinita” variabilidade que os parâmetros nos permitem,
ainda assim existirão sempre limites naquilo que conseguimos fazer apenas
com parâmetros numéricos e, para termos um grau superlativo de variabi-
lidade, teremos de fazer variar as próprias funções.
Nesta secção vamos discutir funções de ordem superior. Uma função de
ordem superior é uma função que recebe funções como argumentos ou que
produz funções como resultados. À parte esta particularidade, uma função
de ordem superior não tem nada de diferente de outra função qualquer.
y(x) = a sin(ωx + φ)
271
272 CAPÍTULO 8. ORDEM SUPERIOR
A função anterior gera uma lista com pontos da fachada. Para modelar-
mos o resto do edifício precisamos de unir esses pontos de modo a formar
uma curva e precisamos de juntar a essa curva os três lados rectilineos de
modo a formar uma região correspondente à planta do edifício. Para isso,
vamos unir os pontos com uma spline e vamos formar uma região entre ela
e as rectas que delimitam os outros três lados do edifício. O comprimento
lx do edifício será dado pela distância horizontal entre os dois extremos
da curva da fachada. Já a largura ly e a altura lz terão de ser parâmetros.
Assim, temos:
edificio(pontos, ly, lz) =
let p0 = pontos[1],
p1 = pontos[end],
lx = p1.x-p0.x
extrusion(surface(spline(pontos),
line(p0,
p0+vxy(0, ly),
p0+vxy(lx, ly),
p1)),
lz)
end
talmente inútil para modelar edifícios cuja fachada segue uma parábola, ou
um logarítmo, ou uma exponencial ou, na verdade, qualquer outra curva
que não seja reduzível a uma sinusóide. De facto, embora o ajuste dos pa-
râmetros nos permita a infinita variabilidade do edifício modelado, ainda
assim, ele será sempre um caso particular de uma fachada sinusoidal.
É claro que nada nos impede de definir outras funções para a modela-
ção de fachadas diferentes. Imaginemos, por exemplo, que pretendemos
uma fachada com a curvatura de uma parábola. A parábola com vértice
em (xv , yv ) e foco em (xv , yv + d), sendo d a distância do vértice ao foco,
define-se pela seguinte equação:
(x − xv )2 = 4d(y − yv )
ou seja
(x − xv )2
y= + yv
4d
Em Julia, esta função define-se como:
parabola(xv, yv, d, x) =
(x-xv)^2/(4*d)+yv
é absolutamente idêntica a
pontos_funcao(p, sinusoide, a, omega, fi, 0, lx, dx)
é absolutamente idêntica a
pontos_funcao(p, parabola, xv, yv, d, 0, lx, dx)
y = ae−bx sin(cx)
ou, em Julia:
oscilatorio_amortecido(a, b, c, x) =
a*exp(-(b*x))*sin(c*x)
edificio(pontos_funcao(xy(0, 0),
oscilatorio_amortecido,
-5, 0.1, 1,
0, 40, 0.4),
10, 20)
Figura 8.3: Um edifício com uma fachada que acompanha a curva do mo-
vimento oscilatório amortecido.
soma_logaritmos(a, b) =
if a > b
0
else
log(a) + soma_logaritmos(a + 1, b)
end
julia> soma_logaritmos(1, 4)
3.17805383035
julia> soma_raizes_quadradas(1, 4)
6.14626436994
soma_???(a, b) =
if a > b
0
else
???(a) + soma_???(a + 1, b)
end
Ora esta definição não é mais que uma soma de uma expressão ma-
temática entre dois limites, i.e., um somatório bi=a f (i). O somatório é
P
uma abstracção matemática para uma soma de números descritos por uma
expressão matemática relativa ao índice do somatório que varia desde o
limite inferior até ao limite superior. A expressão matemática é, portanto,
função do índice do somatório.
O símbolo ??? representa a expressão matemática a realizar dentro do
somatório e que vamos simplesmente transformar num parâmetro, escre-
vendo:
somatorio(f, a, b) =
if a > b
0
else
f(a) + somatorio(f, a + 1, b)
end
julia> somatorio(log, 1, 4)
3.17805383035
julia> somatorio(sqrt, 1, 4)
6.14626436994
100
X
i3 + 2i2 + 5i
i=1
f2(x) =
x*x*x + 2*x*x + 5*x
Vimos que após a definição anterior, o símbolo sqr fica associado a uma
função:
julia> sqr
sqr (generic function with 1 method)
julia> fi
1.618033988749895
podendo ser usado como se tivesse sido definido originalmente como fun-
ção. Na prática, a definição de funções via expressões lambda é equivalente
à definição usual de funções. Mais especificamente,
nome = (parâmetro1 , ..., parâmetron ) -> expressão
é equivalente a:
nome(parâmetro1 , ..., parâmetron ) = expressão
Como vimos, para gerarmos uma lista de pontos de, por exemplo, uma
sinusóide, podemos invocar esta função da seguinte forma:
pontos_funcao(xy(0, 0),
sinusoide,
0.75, 0.5, 0,
0, 15, 0.4)
Notemos que, por ter sido definida como uma generalização das fun-
ções pontos_sinusoide e pontos_parabola, a função pontos_funcao,
8.4. FUNÇÕES ANÓNIMAS 283
spline(
pontos_funcao(xy(0, 3),
x -> -(x/10)^3,
0, 4*pi, 0.5))
spline(
pontos_funcao(xy(0, 0),
x -> oscilatorio_amortecido(1.5, 0.4, 4, x),
0, 4*pi, 0.05))
As varandas são compostas por uma lage e uma guarda, sendo a guarda
composta pelos prumos e pelo corrimão. Defina uma função varanda que,
convenientemente parametrizada, é capaz de gerar não só as varandas apre-
sentadas na imagem anterior mas também muitas outras. Para isso, a fun-
ção varanda deverá receber não só os parâmetros geométricos da varanda
(como seja a espessura da laje ou a altura do corrimão, etc.), mas também a
função que determina a curva exterior da varanda.
Exercício 8.4.2 Defina as funções necessárias e escreva expressões Julia
que reproduzem as varandas apresentadas na imagem anterior.
Exercício 8.4.3 Considere a imagem seguinte onde apresentamos uma sequên-
cia de tubos cilíndricos unidos por esferas que perfazem um caminho ale-
atório em que os troços do caminho são paralelos aos eixos coordenados.
Note que os tubos têm um comprimento aleatório compreendido entre um
comprimento máximo e 10% desse comprimento máximo e que as mudan-
ças de direcção são também aleatórias mas nunca invertem a direcção ime-
diatamente anterior. Note ainda que os tubos possuem um raio que é 2%
do comprimento máximo do tubo e que as esferas possuem um raio que é
4% do comprimento máximo do tubo.
somatorio(f, a, b) =
if a > b
0
else
f(a) + somatorio(f, a + 1, b)
end
imediata será empregar uma expressão lambda. Por exemplo, para calcu-
larmos
10
X sin i
√
i=1
i
podemos escrever:
identidade(x) =
x
b
Y
f (i) = f (a) · f (a + 1) · · · · · f (b − 1) · f (b)
i=a
288 CAPÍTULO 8. ORDEM SUPERIOR
produtorio(f, a, b) =
if a > b
1
else
f(a)*produtorio(f, a + 1, b)
end
factorial(n) =
produtorio(identidade, 1, n)
P10 i
Comecemos por considerar o seguinte somatório: i=0 2 . O valor
desta expressão pode ser calculado em Julia através de:
restricao(f, a) =
x -> f(a, x)
julia> exponencial(2)
7.3890560989306495
(f ◦ g)(t) = f (g(t))
Nesta forma, já é mais óbvio que ◦ é uma função que recebe outras funções
como argumentos. Só por isto, já podemos afirmar que ◦ é uma função de
ordem superior mas, na verdade, ela faz mais do que receber duas funções
como argumentos, ela também produz uma nova função como resultado
que, para um dado argumento t, calcula f (g(t)). Uma vez que esta nova
função não tem nome, a melhor forma de ser produzida é através de uma
expressão lambda. A definição correcta da composição de funções é, então:
8.8.1 Mapeamento
Uma das operações mais úteis é aquela que transforma uma lista noutra
lista através da aplicação de uma função a cada elemento da primeira lista.
Por exemplo, dada uma lista de números, podemos estar interessados em
produzir uma outra lista com os quadrados desses números. Neste caso,
dizemos que estamos a mapear a função quadrado numa lista para produzir
a lista dos quadrados. A definição da função apresenta o já típico padrão
de recursão em listas:
8.8. FUNÇÕES DE ORDEM SUPERIOR SOBRE LISTAS 291
sqr(x) =
x*x
mapeia_quadrado(lista) =
if lista == []
[]
else
[sqr(lista[1]), mapeia_quadrado(lista[2:end])...]
end
mapeia(f, lista) =
if lista == []
[]
else
[f(lista[1]), mapeia(f, lista[2:end])...]
end
Para além desta função, o Julia disponibiliza ainda uma variante sintá-
tica que pode ser útil para evitar escrever funções anónimas: as listas em
compreensão, tal como descrevemos na secção 5.4.1. Usando esta forma, os
cálculos anteriores podem também ser realizados através de:
8.8.2 Filtragem
Uma outra função muito útil é a que filtra uma lista. A filtragem é feita
fornecendo um predicado que é aplicado a cada elemento da lista. Os ele-
mentos que satisfazem o predicado (i.e., em relação aos quais o predicado
é verdade) são colecionados numa nova lista.
A definição é a seguinte:
filtra(predicado, lista) =
if lista == []
[]
elseif predicado(lista[1])
[lista[1], filtra(predicado, lista[2:end])...]
else
filtra(predicado, lista[2:end])
end
8.8.3 Redução
Uma terceira função de grande utilidade é a que realiza uma redução numa
lista. Esta função de ordem superior recebe uma operação, um elemento
inicial, e uma lista e reduz a lista a um valor que resulta de intercalar
a operação entre todos os elementos da lista. Por exemplo, para somar
todos os elementos de uma lista l=[e0 , e1 , ..., en ] podemos fazer
reduz(+, 0, l) e obter e0 +e1 +. . . +en +0. Assim, temos:
reduz(f, v, lista) =
if lista == []
v
else
f(lista[1], reduz(f, v, lista[2:end]))
end
n! = 1 × 2 × 3 × · · · × n
p0,0
p1,0
p2,0
p0,1
p1,1 p0,2
p3,0 p1,2 p0,3
p2,1 p1,3
p2,2
p4,0 p2,3
p3,1 p0,4
p5,0 p3,2 p1,4 p0,5
p3,3 p1,5
p4,1 p2,4
p4,2 p2,5
p5,1 p4,3
p5,2 p3,4
p5,3 p3,5
p4,4
p4,5
p5,4
p5,5
julia> all_shapes()
[]
julia> circle(xy(1, 2), 3)
<circle 0>
julia> sphere(xyz(1, 2, 3), 4)
<sphere 1>
julia> all_shapes()
[<solid 2>, <circle 3>]
Figura 8.5: Planta fornecida pelos serviços camarários. Cada edifício é re-
presentado pelo seu polígono envolvente e por um ponto à cota do topo
de edifício. Por erros nas plantas, alguns edifícios podem não possuir a
cota respectiva enquanto que outros podem possuir mais do que uma cota.
Pode também haver cotas que não estão associadas a qualquer edifício.
point_position(point(p)) = p
circle_center(circle(p, r)) = p
circle_radius(circle(p, r)) = r
irá encontrar edifícios que não possuem qualquer cota, assim como cotas
que não estão associadas a qualquer edifício. Para além disso, é possível
encontrar edifícios com mais do que uma cota. Estes são problemas que
teremos de ter em conta quando pensarmos na criação de programas que
manipulem estas plantas.
Para criarmos um modelo tridimensional a partir de uma destas plantas
podemos, para cada polígono, formar uma região que vamos extrudir até
à cota indicada pelo ponto correspondente. Para isso, temos de conseguir
identificar, para cada polígono, qual é esse ponto (ou quais são os pontos,
no caso de haver mais do que um).
Detectar se um ponto está contido no interior de um polígono é um
problema clássico da Geometria Computacional para o qual existem várias
soluções. Uma das mais simples consiste em traçar um raio a partir desse
ponto e contar o número de intersecções que esse raio faz com as arestas
do polígono, tal como se ilustra na Figura 8.6 para vários pontos. Se o
número de intersecções for zero, então o ponto está obviamente fora do
polígono. Se o número for um, então o ponto está necessariamente dentro
do polígono. Se esse número for dois, então é porque o raio entrou e saiu
do polígono e, portanto, o ponto está fora do polígono; se o número for três,
então é porque o raio saiu, entrou e voltou a sair do polígono e, portanto, o
ponto está no interior do polígono. Uma vez que cada entrada do raio no
polígono implica a sua posterior saída, torna-se evidente que se o número
de intersecções do raio com o polígono for par, então é porque o ponto
estava fora do polígono e se o número de intersecções for ímpar então é
porque o ponto estava no interior do polígono.
A implementação deste algoritmo é relativamente simples: dado um
298 CAPÍTULO 8. ORDEM SUPERIOR
caso de não existir intersecção. Uma vez que apenas pretendemos conhecer
as intersecções, podemos agora filtrar esta lista, ficando apenas com os que
são pontos, i.e., os que não forem falsos:
ponto_no_poligono(p, vs) =
let q = xy(reduce(max, map(cx, vs)) + 1, p.y)
rs = filter(e -> e != nothing,
map((vi, vj) -> interseccao_segmentos(p, q, vi, vj),
vs,
[vs[2:end]..., vs[1]]))
...
end
pontos_no_poligono(pts, vs) =
filter(pt -> ponto_no_poligono(xyz(pt.x, pt.y, vs[1].z), vs),
pts)
• Se esse número é zero, não sabemos qual a cota do edifício. Isso re-
presenta um erro na planta que podemos não querer tratar ou que
podemos tratar simplesmente arbitrando uma altura. Por agora, va-
mos escolher nada fazer.
cria_edificios(pontos, poligonos) =
for poligono in poligonos
let pts = pontos_no_poligono(pontos, poligono),
n_pts = length(pts)
if n_pts == 0
nothing
elseif n_pts == 1
cria_edificio(poligono, cz(pts[1]))
else
cria_edificio(poligono, reduce(max, map(cz, pts)))
end
end
end
cidade(entidades) =
cria_edificios(pontos(entidades), poligonos(entidades))
cidade(all_shapes())
Representação Paramétrica
9.1 Introdução
Até agora, apenas temos produzido curvas descritas por funções na forma
y = f (x). Estas funções dizem-se na forma Cartesiana.
Uma outra forma de representar matematicamente uma curva é atra-
vés de equações F (x, y) = 0. Esta forma, dita implícita, é mais geral que
a anterior que, na verdade, não é mais que a resolução desta em relação
à ordenada y. Como exemplo de uma curva descrita na forma implícita
consideremos a equação x2 + y 2 − r2 = 0 que descreve uma circunferência
de raio r com centro em (0, 0). Infelizmente, esta segunda forma de repre-
sentar curvas não é tão útil como a anterior pois nem sempre é trivial (ou
possível) resolver a equação em relação à ordenada.1 Por exemplo, no caso
da circunferência, o melhor que conseguimos fazer é produzir duas funções:
p
y(x) = ± r2 − x2
x(t) = r cos t
y(t) = r sin t
Como é óbvio, para que o ponto de coordenadas (x, y) possa percorrer toda
a circunferência, basta que o parâmetro t varie no intervalo [0, 2π[.
1
Excepto no caso de rectas, cuja equação geral é ax + by + c = 0, b 6= 0 e onde é óbvio
que a resolução em relação à ordenada é y = − ax+c
b
.
305
306 CAPÍTULO 9. REPRESENTAÇÃO PARAMÉTRICA
pontos_circulo(p, r, dt) =
map(t -> p + vpol(r, t), enumera(0, 2*pi, dt))
um erro que faz com que o último valor calculado seja, afinal, superior ao
limite, pelo que é descartado.2
Para evitar estes problemas seria natural pensarmos que nos devemos
limitar a usar incrementos sob a forma de fracções mas, infelizmente, isso
nem sempre é possível. Por exemplo, quando usamos funções trigonomé-
tricas (como senos e cossenos), é usual termos de gerar valores num inter-
valo que corresponde a um múltiplo do período das funções que, como
sabemos, envolve o número irracional π, não representável como fracção.
Assim, para lidarmos correctamente com números reais, devemos usar ou-
tra abordagem que se baseie em produzir o número de valores realmente
pretendido, evitando adições sucessivas. Para isso, ao invés de usarmos o
incremento como parâmetro, iremos antes usar o número de valores pre-
tendidos.
Na sua forma actual, a partir dos limites t0 e t1 e do incremento ∆t , a
função enumera gera cada um dos pontos ti calculando:
ti = t0 + ∆t + ∆t + · · · + ∆t ≡
| {z }
i vezes
i
X
ti = t0 + ∆t
j=0
t1 − t0
n=
∆t
Consequentemente, temos também
t1 − t0
∆t =
n
Substituindo na equação anterior, obtemos
i
X
ti = t0 + ∆t ≡
j=0
2
Este fenómeno tem sido causa de inúmeros problemas no mundo da informática. Desde
erros nos sistemas de defesa anti-míssil ao cálculo errado de índices bolsistas, a história da
informática está repleta de exemplos catastróficos causados por erros de arredondamento.
310 CAPÍTULO 9. REPRESENTAÇÃO PARAMÉTRICA
i
X t1 − t0
ti = t0 + ≡
n
j=0
i
t1 − t0 X
ti = t0 + 1≡
n
j=0
t1 − t0
ti = t0 + i
n
O aspecto fundamental da última equação é que ela já não corresponde
a uma soma de i termos onde pode ocorrer uma acumulação de erros de
arredondamento, mas sim a uma “função” f (i) = t1 −t n i de i, em que i não
0
enumera_n(t0, t1, n) =
map(i -> t0 + i*(t1 - t0)/n, enumera(0, n, 1))
julia> division(0, 1, 4)
[0.0, 0.25, 0.5, 0.75, 1.0]
julia> division(0, pi, 4)
[0.0,
0.7853981633974483,
1.5707963267948966,
2.356194490192345,
3.141592653589793]
Dividindo a curva em duas metades para lidar com os dois sinais, te-
mos:
meia_espiral_fermat(p, a, t, n) =
[p + vpol(a*sqrt(t), t) for t in division(0, t, n)]
espiral_fermat(p, a, t, n) =
[reverse(meia_espiral_fermat(p, +a, t, n))...,
meia_espiral_fermat(p, -a, t, n)[2:end]...]
3
Este modelo foi proposto por H. Vogel em 1979.
4
Este índice é inversamente proporcional à ordem de crescimento da semente.
314 CAPÍTULO 9. REPRESENTAÇÃO PARAMÉTRICA
girassol(p, a, d, r, n) =
for t in division(1, n, n - 1)
circle(p + vpol(a*sqrt(t), d*t), r)
end
y 2 (2a − x) = x3 , x ∈ [0, a[
ou seja,
2a(1 − cos2 φ) − ρ cos φ + ρ cos3 φ = ρ cos3 φ
Simplificando, obtemos finalmente
1
ρ = 2a( − cos φ)
cos φ
Uma vez que cos ± π2 = 0, a curva tende para infinito para aqueles valores.
Isto delimita o intervalo de variação a φ ∈] − π2 , + π2 [.
Obviamente, para transformarmos a representação polar numa repre-
sentação paramétrica fazemos simplesmente
(x2 + y 2 )2 = a2 (x2 − y 2 )
Figura 9.9: A superelipse proposta por Piet Hein para a praça Sergels, em
Estocolmo. Fotografia de Nozzman.
9.4. MAPEAMENTOS E ENUMERAÇÕES 321
sgn(x) =
-1 ? x < 0 : 0 ? x == 0 : 1
superelipse(p, a, b, n, t) =
p + vxy(a*(cos(t)^2)^(1/n)*sgn(cos(t)),
b*(sin(t)^2)^(1/n)*sgn(sin(t)))
pontos_superelipse(p, a, b, n, pts) =
[superelipse(p, a, b, n, t) for t in division(-pi, pi, pts, false)]
curva_superelipse(p, a, b, n, pts) =
closed_spline(pontos_superelipse(p, a, b, n, pts))
testa_superelipses(a, b, ns) =
for num in ns
for den in ns
curva_superelipse(xy(num*2.5*a, den*2.5*b), a, b, num/den, 100)
end
end
Usando a hélice cónica torna-se fácil construir uma das curvas mais fa-
mosas dos tempos modernos: a dupla hélice que representa a estrutura mo-
lecular do ADN e que foi descoberta por James D. Watson e Francis Crick
na década de cinquenta do século passado. Para isso, basta avaliar duas
expressões:
spline(pontos_helice_conica(u0(), 1, 0, 0, 3, 0, 1, 100))
spline(pontos_helice_conica(u0(), 1, 0, pi, 3, 0, 1, 100))
9.6 Precisão
Consideremos a curva Borboleta, proposta pelo matemático Temple H. Fay.
Esta curva periódica define-se pela equação em coordenadas polares
sin φ 5 2φ − π
r=e − 2 cos(4φ) + sin
24
t ∈ [0, 24π]. Uma vez que este período é relativamente grande, o número
n de pontos a empregar deverá ser suficiente para que se consiga reprodu-
zir fielmente a curva e é precisamente aqui que surge a maior dificuldade:
sem conhecer a forma da curva, é difícil estimar qual o melhor valor para
n. Se o valor de n for demasiado baixo, a curva não será representada com
fidelidade, em particular, nos locais em que existem curvaturas muito acen-
tuadas; se o valor for demasiado alto, maior será a necessidade de recursos
computacionais. Na prática, é necessária alguma experimentação para per-
ceber qual o melhor valor.
Esta necessidade de experimentação relativamente ao valor do número
de pontos a empregar está bem patente na Figura 9.11 que mostra as curvas
Borboleta desenhada para os seguintes crescentes valores de n:
P1
Pm
P0
t0 tm t1
empregar mais pontos de amostragem, mas nas zonas onde a variação for
mais linear, podemos diminuir o número de pontos de amostragem. Desta
forma, o número de pontos de amostragem adapta-se à forma da curva.
Para que possamos empregar uma amostragem adaptativa precisamos
de definir um critério para classificar a “suavidade” de uma curva. Tal
como ilustramos na Figura 9.13, podemos começar por admitir que temos
duas abcissas x0 e x1 que usamos para calcular os pontos P0 e P1 . Para
sabermos se a curva se comporta de forma linear entre esses dois pontos
(podendo assim ser aproximada por um segmento de recta), vamos cal-
cular o valor médio xm desse intervalo e o correspondente ponto Pm da
curva. Se a função for realmente linear nesse intervalo, então os pontos P0 ,
Pm e P1 serão colineares. Na prática, isso raramente acontecerá, pelo que
teremos de empregar um conceito de colinearidade aproximada, o que po-
demos fazer empregando qualquer um de vários critérios possíveis como,
por exemplo:
Outros critérios serão igualmente aplicáveis, mas, por agora, vamos es-
colher o primeiro. A sua tradução para Julia é a seguinte:
330 CAPÍTULO 9. REPRESENTAÇÃO PARAMÉTRICA
area_triangulo(a, b, c) =
let s = (a + b + c)/2.0
sqrt(s*(s - a)*(s - b)*(s - c))
end
pontos tem área zero, o que leva o algoritmo a terminar de imediato. Mo-
difique a função de modo que ela receba um parâmetro adicional ∆t que
determina o maior intervalo admissível entre dois valores consecutivos de
t.
9.7. SUPERFÍCIES PARAMÉTRICAS 331
exemplo, (x(u, v), y(u, v), z(u, v)). Naturalmente, a escolha de coordena-
das rectangulares, cilíndricas, esféricas ou outras é apenas uma questão de
conveniência: em qualquer caso, serão necessárias três funções.
Tal como acontece com as curvas, embora possamos descrever uma su-
perfície na forma implícita, a representação paramétrica é mais adequada à
geração das posições por onde passa a superfície. Por exemplo, uma esfera
com centro no ponto (x0 , y0 , z0 ) e raio r, pode ser descrita implicitamente
pela equação
(x − x0 )2 + (y − y0 )2 + (z − z0 )2 − r2 = 0
mas esta forma não permite a geração directa de coordenadas, sendo pre-
ferível adoptar uma descrição paramétrica, pelas funções
x(φ, ψ) = x0 + r sin φ cos ψ
y(φ, ψ) = y0 + r sin φ sin ψ
z(φ, ψ) = z0 + r cos φ
era equivalente a
map(f,
division(t0 , t1 , n))
é equivalente a
map(u -> map(v -> f(u, v)
division(v0 , v1 , m)),
division(u0 , u1 , n))
splines(ptss) =
map(spline, ptss)
Para isso, podemos definir uma função que, dada uma matriz imple-
mentada como uma lista de listas, devolve a transposta dessa matriz na
mesma implementação:
matriz_transposta(matrix) =
[[row[i] for row in matrix]
for i in 1:length(matrix[1])]
malha_splines(ptss) =
begin
splines(ptss)
splines(matriz_transposta(ptss))
end
9.8 Superfícies
Os modelos de arame não constituem superfícies. De facto, por as linhas
serem infinitamente finas e não existir qualquer “material” entre elas, não
é possível associar um modelo de arame a uma verdadeira superfície.
Se o que pretendemos é, de facto, desenhar superfícies, então é preferí-
vel explorar as capacidades do Khepri para a criação de superfícies.
As malhas poligonais são aglomerados de polígonos, geralmente triân-
gulos e quadriláteros, que definem a forma de um objecto. Relativamente
aos modelos constituídos apenas por linhas, as malhas poligonais têm a
vantagem de poderem ser mais realisticamente visualizadas, por exemplo,
com remoção de superfícies invisíveis, com inclusão de sombreados, etc.
Obviamente, cada face destas malhas poligonais possui espessura zero,
pelo que não são verdadeiros sólidos mas apenas representações abstrac-
tas de superfícies. Ainda assim, podem ser muito úteis para se obter uma
mais correcta visualização de uma superfície e podem ser subsequente-
mente transformadas para a criação de sólidos, por exemplo, através da
função thicken que confere uma espessura uniforme à superfície.
9.8. SUPERFÍCIES 339
x2 − y 2 − z = 0
thicken(
surface_grid(
map_division((x, y) -> xyz(x, y, x*x - y*y),
-0.5, 1.3, 40, -2, 2, 80)),
0.03)
9.8.1 Helicoide
Discutimos, na secção 9.5, o desenho da hélice. Vamos agora discutir a
superfície que generaliza aquela curva: a helicóide.
A helicóide foi descoberta em 1776 por Jean Baptiste Meusnier. As suas
equações paramétricas (em coordenadas cilíndricas) são:
ρ(u, v) = u
θ(u, v) = αv
z(u, v) = v
9.8.2 Mola
Uma mola é outra figura geométrica que possui afinidades com uma hélice.
Matematicamente, a mola é o volume gerado por um círculo que se desloca
ao longo de uma hélice. Em termos mais simples, uma mola é uma hélice
com “espessura,” i.e., um tubo que se enrola ao longo de um eixo. Se for
r0 o raio do tubo, r1 a distância do eixo do tubo ao eixo da hélice, α o
ângulo de rotação inicial da mola e vz a “velocidade” ao longo do eixo Z,
as equações paramétricas da mola são:
ρ(u, v) = r1 + r0 cos u
θ(u, v) = α + v
z(u, v) = vz v + r0 sin u
π
surface_grid(
mola(xyz(0, 0, 0), 0, 1, 5, 1, 0, 2*pi, 50, 0, 3*2*pi, 150))
surface_grid(
mola(xyz(20, 0, 0), 0, 1, 5, 2, 0, 2*pi, 50, 0, 3*2*pi, 150))
surface_grid(
mola(xyz(40, 0, 0), 0, 1, 5, 4, 0, 2*pi, 50, 0, 3*2*pi, 150))
r1
r0 α r0
α
r1
corda(xyz(0, 0, 0), 2, 1, 3)
corda(xyz(10, 0, 0), 3, 1, 2)
corda(xyz(20, 0, 0), 6, 1, 1)
2 1 − a2 cosh(au) sinh(au)
x(u, v) = −u + √
2 2 2 2 2
a (1 − a ) cosh (au) + a sin 1−a v
√ √ √ √
2 cosh(au) − 1 − a2 cos(v) cos 2 v − sin(v) sin 2v
2 1 − a 1 − a 1 − a
y(u, v) = √
a (1 − a 2 ) cosh2 (au) + a2 sin2 1 − a2v
√ √ √ √
2 cosh(au) − 1 − a2 sin(v) cos 2 v + cos(v) sin 2v
2 1 − a 1 − a 1 − a
z(u, v) =
√
a (1 − a2 ) cosh2 (au) + a2 sin2 1 − a2 v
surface_grid(
breather(xyz(0, 0, 0), 0.4, -13.2, 13.2, 200, -37.4, 37.4, 200))
9.8. SUPERFÍCIES 347
9.8.3 Conchas
Vários autores defendem que a Natureza é uma manifestação matemática e
justificam essa crença pela surpreendente semelhança que algumas super-
fícies matemáticas apresentam com formas naturais.
A superfície de Dini, é um desses casos. As equações paramétricas que
a definem são:
x(u, v) = cos(u) sin(v)
y(u, v) = sin(u) sin(v)
z(u, v) = cos(v) + log(tan( v )) + a ∗ u
2
em que 0 ≤ u ≤ 2π e 0 ≤ v ≤ π.
A tradução destas equações para Julia é directa:
dini(p, a, u0, u1, m, v0, v1, n) =
map_division((u, v) -> p + vxyz(cos(u)*sin(v),
sin(u)*sin(v),
cos(v) + log(tan(v/2.0)) + a*u),
u0, u1, m,
v0, v1, n)
surface_grid(
concha(xyz(0, 0, 0), 1, 1, 0, 7*pi, 100, 0, 2*pi, 100))
surface_grid(
concha(xyz(7, 0, 0), 2, 2, 0, 7*pi, 100, 0, 2*pi, 100))
surface_grid(
concha(xyz(18, 0, 0), 3, 1, 0, 7*pi, 100, 0, 2*pi, 100))
mente muito mais simples, o que nos permitirá perceber que a construção
paramétrica de superfícies é relativamente fácil desde que compreendamos
o impacto da variação dos parâmetros independentes u e v.
Por exemplo, a superfície de um cilindro de raio r caracteriza-se por ser
o conjunto de pontos que estão à distância fixa r de um eixo e entre uma
“altura” mínima h0 e máxima h1 relativamente a esse eixo.
Admitindo, por simplicidade, que vamos fazer coincidir o eixo do ci-
lindro com o eixo Z, isto quer dizer que podemos empregar coordenadas
cilíndricas (ρ, φ, z), ficando a superfície do cilindro definida simplesmente
fixando ρ = r e deixando φ variar entre 0 e 2π e z variar entre h0 e h1 . Es-
tando ρ fixo e sendo as variações de φ e z independentes, a utilização de
superfícies paramétricas é simples: basta usar um dos parâmetros u ou v
para fazer a variação de φ e o outro para a variação de z.
Mais especificamente, vamos fazer ρ(u, v) = r e vamos deixar φ(u, v) =
u e z(u, v) = v variar independentemente, respectivamente entre u ∈ [0, 2π]
e v ∈ [h0 , h1 ]. Uma vez que φ depende directa e exclusivamente de u e z
depende directa e exclusivamente de v, podemos usar estes parâmetros
directamente na função. Desta forma, estamos em condições de escrever
uma função que produza uma coordenada cilíndrica a partir do valor do
raio r e a partir dos parâmetros independentes φ e z.
A imagem da esquerda da Figura 9.28 mostra o cilindro de raio 1 entre
h0 = −1 e h1 = 1, produzido pela avaliação da seguinte expressão:
surface_grid(
map_division(
(fi, z) -> cyl(1, fi, z),
0, 2*pi, 60,
-1, 1, 20))
surface_grid(
map_division(
(fi, psi) -> sph(1, fi, psi),
0, 2*pi, 60,
0, pi, 30))
x = a sin ψ cos φ
y = b sin ψ sin φ
z = c cos ψ
− π2 ≤ φ ≤ + π2 ; −π ≤ ψ ≤ +π;
Defina a função elipsoide que produz o elipsóide a partir dos três
raios a, b, c e, ainda, do número n de valores a usar ao longo de φ e de ψ.
tral está já correctamente modelada, mas é preciso evitar que esta evolução
afecte também as partes laterais. Para isso, basta-nos fazer uma combina-
ção entre duas evoluções distintas, uma para a região central da parede,
correspondente a meio ciclo do cosseno, i.e., a uma gama de variação em x
entre − π2 e + π2 , e outra para tudo o resto. Temos de ter em conta que para
compensar a diferença de declive provocada pelo aumento da coordenada
y na região central temos de aumentar igualmente a amplitude da variação
em z. Assim, temos:
surface_grid(
map_division(
(x, z) -> xyz(x,
-pi/2 <= x <= pi/2 ?
(z + 3)*0.8*cos(x) :
cos(x),
-pi/2 <= x <= pi/2 ?
z*(1 + 0.4*cos(x)) :
z*(1 + 0.2*cos(x))),
-7*pi, 7*pi, 100,
0, 5, 10))
P2
P3
y
x
P1
P0
dx
x P3
y
P0
P2
P1
Exercício 9.9.7 A “maçã” apresentada abaixo pode ser descrita pela equa-
ção paramétrica
0 ≤ u ≤ 2π; −π ≤ v ≤ π;
P3
P0
M13 M02
M
P2
P1
P3
P3 P2 P2
P1
P0 P1 P0
produtos_cruzados(vs) =
if length(vs) == 1
vxyz(0, 0, 0)
else
cross(vs[1], vs[2]) + produtos_cruzados(vs[2:end])
end
superficie_e_normais(ptss) =
begin
normais(ptss)
surface_grid(ptss)
end
Uma observação cuidada da função normais mostra que ela itera ao longo
dos quadrângulos da superfície, computando a normal em cada um. Natu-
ralmente, podemos generalizar este processo de modo a que seja possível
processar uma superfície através da aplicação de uma qualquer operação a
todos os seus quadrângulos. A passagem para uma função de ordem su-
perior é feita substituindo a função que calcula a normal por uma função
arbitrária:
9.11. PROCESSAMENTO DE SUPERFÍCIES 365
itera_quadrangulos(f, ptss) =
[[f(p0, p1, p2, p3)
for (p0, p1, p2, p3)
in zip(pts0[1:end-1], pts1[1:end-1], pts1[2:end], pts0[2:end])]
for (pts0, pts1)
in zip(ptss[1:end-1], ptss[2:end])]
normais(ptss) =
itera_quadrangulos(normal, ptss)
Vimos nos exemplos anteriores uma separação entre a geração das coor-
denadas de uma superfície (usando, por exemplo, a função map_division)
e o uso dessas coordenadas, por exemplo, para visualização (usando a fun-
ção surface_grid) ou para processamento (usando a função itera_quadrangulos).
Esta separação é vantajosa porque nos permite combinar arbitrariamente
diferentes funções de geração de coordenadas com diferentes funções que
usam essas coordenadas. Uma outra possibilidade interessante é a das fun-
ções que processam as coordenadas para produzirem novas coordenadas.
Um exemplo trivial seria a aplicação de um efeito de escala sobre uma su-
perfície, simplesmente multiplicando a escala por cada uma das coordena-
das, gerando assim um novo conjunto de coordenadas.
370 CAPÍTULO 9. REPRESENTAÇÃO PARAMÉTRICA
insere_vertice_piramide(ptss) =
if length(ptss) == 1
ptss
else
[ptss[1],
insere_vertice_piramide_2(ptss[1], ptss[2]),
insere_vertice_piramide(ptss[2:end])...]
end
insere_vertice_piramide_2(pts0, pts1) =
[vertice_piramide_quadrangular(pts0[1], pts1[1], pts1[2], pts0[2]),
(length(pts0) == 2 ?
[] :
insere_vertice_piramide_2(pts0[2:end], pts1[2:end]))...]
375
376 EPÍLOGO
Bibliografia
[3] IEEE Std 1178-1990. Ieee Standard for the Scheme Programming Language.
Institute of Electrical and Electronic Engineers, Inc., New York, NY,
1991.
[4] Harold Abelson, Gerald Jay Sussman, and Julie Sussman. Structure
and Interpretation of Computer Programs. MIT Press, Cambridge, Mass.,
1985.
[6] Jane Burry and Mark Burry. The new mathematics of architecture. Thames
& Hudson London, 2010.
[8] Cristiano Ceccato, Lars Hesselgren, and Mark Pauly. Advances in Ar-
chitectural Geometry 2010. Springer, 2010.
377
378 BIBLIOGRAFIA
[18] Matthias Felleisen, Robert Bruce Findler, Matthew Flatt, and Shiram
Krishnamurthi. How to Design Programs. The MIT Press, 2003.
[21] Gary William Flake. The Computational Beauty of Nature: Computer Ex-
plorations of Fractals, Chaos, Complex Systems, and Adaptations. The MIT
Press, 1998.
[22] Daniel P. Friedman and Matthias Felleisen. The Little LISPer: Second
Edition. Science Research Associates, Inc., Palo Alto, California, 1986.
[23] Daniel P. Friedman and Matthias Felleisen. The Little LISPer. MIT
Press, 1987.
BIBLIOGRAFIA 379
[24] Daniel P. Friedman and Matthias Felleisen. The Seasoned Schemer. MIT
Press, 1996.
[27] Dominik Holzer, Richard Hough, and Mark Burry. Parametric design
and structural optimisation for early design exploration. International
Journal of Architectural Computing, 5(4):625–643, 2007.
[31] Branko Kolarevic. Architecture in the digital age: design and manufactu-
ring. Taylor & Francis, 2003.
[35] Michael Meredith and Mutsuro Sasaki. From control to design: parame-
tric/algorithmic architecture. Actar-D, 2008.
[36] Farshid Moussavi, Michael Kubo, and Seth Hoffman. The function of
ornament. Actar, 2006.
[37] Farshid Moussavi and Daniel López. The function of form. Actar Barce-
lona, 2009.
[40] Helmut Pottmann, Michael Hofer, and Axel Kilian. Advances in ar-
chitectural geometry. In Proc. of Vienna conference, 2008.
[41] Casey Reas. Form+code in design, art, and architecture. Princeton Archi-
tectural Press, 2010.
[42] Casey Reas and Ben Fry. Processing: programming for the media arts.
AI & SOCIETY, 20(4):526–538, 2006.
[43] Casey Reas and Ben Fry. Processing: a programming handbook for visual
designers and artists, volume 6812. Mit Press, 2007.
[44] Casey Reas and Ben Fry. Getting Started with Processing. O’Reilly, 2010.
[46] Jonathan A. Rees, Norman I. Adams, and James R. Meehan. The T Ma-
nual, Fourth Edition. Yale University Computer Science Department,
January 1984.
[48] George Springer and Daniel P. Friedman. Scheme and the Art of Pro-
gramming. MIT Press and McGraw-Hill, 1989.
[50] Lars Spuybroek. Research & design: the architecture of variation. Thames
& Hudson, 2009.
[52] Helmut Vogel. A better way to construct the sunflower head. Mathe-
matical biosciences, 44(3-4):179–189, 1979.
[53] Robert Woodbury et al. Elements of parametric design. Taylor & Francis
Group, 2010.
Soluções
381
382 SOLUÇÕES
sinh(x) =
(exp(x) - exp(-x))/2
cosh(x) =
(exp(x) + exp(-x))/2
tanh(x) =
(exp(x) - exp(-x))/(exp(x) + exp(-x))
acosh(x) =
log(x + sqrt(x*x - 1))
atanh(x) =
if abs(x) < 1
log((1 + x)/(1 - x))/2
else
log((x + 1)/(x - 1))/2
end
385
Solução do Exercício 2.5.1 Este problema obriga-nos a trabalhar com tolerâncias, de modo
que as comparações não sejam feitas em termos absolutos mas sim relativamente a uma
dada tolerância , i.e., ao invés de a = b, testamos ka − bk ≤ . Comecemos então por
definir a tolerância para a comparação de coordenadas:
tolerancia_posicoes = 1e-15
posicoes_iguais(p0, p1) =
distancia(p0, p1) <= tolerancia_posicoes
1
α
ρ
obelisco_perfeito(u0(), 168)
torre_sears(p, l, h00, h01, h02, h10, h11, h12, h20, h21, h22) =
begin
bloco_sears(p, 0, 0, l/3, h00)
bloco_sears(p, 0, 1, l/3, h01)
bloco_sears(p, 0, 2, l/3, h02)
bloco_sears(p, 1, 0, l/3, h10)
bloco_sears(p, 1, 1, l/3, h11)
bloco_sears(p, 1, 2, l/3, h12)
bloco_sears(p, 2, 0, l/3, h20)
bloco_sears(p, 2, 1, l/3, h21)
bloco_sears(p, 2, 2, l/3, h22)
end
torre_sears(u0(), 68.7, 270, 442, 205, 368, 442, 368, 205, 368, 270)
388 SOLUÇÕES
cyl_phi(p) =
atan2(cy(p), cx(p))
cyl_z(p) =
cz(p)
escada(p, r, h, a) =
begin
cylinder(p, r, p + vxyz(0, 0, 10*h))
degrau(p, r, a*0, h*1)
degrau(p, r, a*1, h*2)
degrau(p, r, a*2, h*3)
degrau(p, r, a*3, h*4)
degrau(p, r, a*4, h*5)
degrau(p, r, a*5, h*6)
degrau(p, r, a*6, h*7)
degrau(p, r, a*7, h*8)
degrau(p, r, a*8, h*9)
end
sph_phi(p) =
atan2(cy(p), cx(p))
sph_psi(p) =
atan2(sqrt(cx(p)^2 + cy(p)^2), cz(p))
389
r1
r α
2
r0
circulos(p, raio) =
if raio < 1
nothing
else
circle(p, raio)
circulos(p + vx(raio), raio/2)
circulos(p + vy(raio), raio/2)
end
losangos(p, c) =
if c < 1
nothing
else
let p0 = p + vpol(c, 0),
p1 = p + vpol(c, pi/2),
p2 = p + vpol(c, pi),
p3 = p + vpol(c, 3*pi/2),
c2 = c/2.0
line(p0, p1, p2, p3, p0)
losangos(p0, c2)
losangos(p1, c2)
losangos(p2, c2)
losangos(p3, c2)
end
end
escada_rampa(p, alfa, c, n) =
if n == 0
nothing
else
let e = c*tan(alfa),
p1 = p + vy(e),
p2 = p1 + vx(c)
line(p, p1, p2)
escada_rampa(p2, alfa, c, n - 1)
end
end
392 SOLUÇÕES
Solução do Exercício 3.3.1 Dados os pontos inicial P e final Q, é fácil constatarmos que o
vector v que mede a separação entre as n colunas é determinado por
Q−P
~v =
n
Assim, podemos definir uma função que coloca as colunas entre dois pontos quaisquer p e
q:
colunas_doricas_entre(p, q, h, n) =
colunas_doricas(p, h, (q - p)/n, n)
cidade_espacial(p, raio) =
begin
sphere(p, raio/8.0)
if raio < 1
nothing
else
let r2 = raio/2,
nx = p - vx(r2),
px = p + vx(r2),
ny = p - vy(r2),
py = p + vy(r2),
nz = p - vz(r2),
pz = p + vz(r2)
cylinder(nx, raio/32.0, px)
cylinder(ny, raio/32.0, py)
cylinder(nz, raio/32.0, pz)
cidade_espacial(nx, r2)
cidade_espacial(px, r2)
cidade_espacial(ny, r2)
cidade_espacial(py, r2)
cidade_espacial(nz, r2)
cidade_espacial(pz, r2)
end
end
end
cidade_espacial(xyz(0, 0, 0), 8)
Solução do Exercício 3.4.2 Para que os arcos de circunferência possam interligar-se sua-
vemente, é necessário que exista uma relação entre os raios r0 , r1 e r2 . É facil ver que
(r2 − r1 ) cos α + r0 = r2
h = r0 + (r2 − r1 ) sin α + r1
Uma vez que a função não recebe nem o raio r2 nem o ângulo α, temos de os deduzir. Para
isso, vamos resolver a primeira equação em ordem a r2
r0 − r1 cos α
r2 =
1 − cos α
r0 − r1 cos α
h = r0 + ( − r1 ) sin α + r1
1 − cos α
Simplificando, obtemos
sin α
h = r0 + r1 + (r0 − r1 )
1 − cos α
Empregando agora a igualdade trigonométrica
α 1 − cos α
tan =
2 sin α
temos
1
h = r0 + r1 + (r0 − r1 ) α
tan 2
ou seja,
r0 − r1
α = 2 tan−1
h − r0 − r1
Estamos agora em condições de definir a função pretendida:
ovo(p, r0, r1, h) =
let alfa = 2*atan2(r0 - r1, h - r0 - r1),
r2 = (r0 - r1*cos(alfa))/(1 - cos(alfa))
arc(p, r0, 0, -pi)
arc(p + vx(r0 - r2), r2, 0, alfa)
arc(p + vx(r2 - r0), r2, pi - alfa, alfa)
arc(p + vy((r2 - r1)*sin(alfa)), r1, alfa, pi - alfa - alfa)
end
ramo(p, topo) =
let raio = distance(p, topo)/10.0
cone_frustum(p, raio, topo, raio*0.9)
end
cilindros_aleatorios(n) =
if n == 0
nothing
else
cylinder(posicao_aleatoria(), random_range(1, 10), posicao_aleatoria())
cilindros_aleatorios(n - 1)
end
caminhada_aleatoria(u0(), 5, 100)
direccao_ortogonal_aleatoria_excepto(v0) =
let v1 = round_vector(sph(1, random_range(0, 4)*pi/2, random_range(0, 3)*pi/2))
if v0 == v1 || v0 == v1*-1
direccao_ortogonal_aleatoria_excepto(v0)
else
v1
end
end
blocos_conexos(p, v, c, l, n) =
if n == 0
nothing
else
right_cuboid(p, l, l, p + v*c)
let v1 = direccao_ortogonal_aleatoria_excepto(v),
p1 = p + v*(c - l/2.0) + v1*l/2.0
blocos_conexos(p1, v1, c, l, n - 1)
end
end
predio(p, l, h) =
let h = h*max(0.1, gaussiana_2d(cx(p), cy(p), 25.0*l))
if random(5) == 0
predio1(p, l, h)
else
predio0(p, l, h)
end
end
random_element(l) =
l[random(length(l))+1]
misterio(ps, n) =
let p = ps[0]
for i in range(n):
let p = intermediate_loc(p, random_element(ps))
surface_circle(p, 1)
end
end
divisao_aleatoria_poligonos(pols, n) =
if pols == []
[]
else
[divisao_aleatoria_poligono(pols[1], n)...,
divisao_aleatoria_poligonos(pols[2:end], n)...]
end
divisao_aleatoria_poligono(pts, n) =
if n == 0
[pts]
else
divisao_aleatoria_poligonos(bisseccao_aleatoria_poligono(pts), n-1)
end
coordenadas_x(p, l, n) =
if n == 0
[]
else
[p, coordenadas_x(p+vx(l), l, n-1)...]
end
trelica_recta(p, h, l, n) =
trelica(coordenadas_x(p, l, n),
coordenadas_x(p+vxyz(l/2, l/2, h), l, n-1),
coordenadas_x(p+vy(l), l, n))
401
l
h= √
2
trelica_modulo(p, l, n) =
trelica_recta(p, l/sqrt(2), l, n)
trelica_plana(ais, bis) =
begin
nos_trelica(ais)
nos_trelica(bis)
barras_trelica(ais, bis)
barras_trelica(bis, ais[2:end])
barras_trelica(ais, ais[2:end])
barras_trelica(bis, bis[2:end])
end
media(a, b) =
(a+b)/2
ponto_medio(p, q) =
xyz(media(cx(p), cx(q)), media(cy(p), cy(q)), media(cz(p), cz(q)))
pontos_medios(ps, qs) =
if ps == []
[]
else
[ponto_medio(ps[1], qs[1]), pontos_medios(ps[2:end], qs[2:end])...]
end
Solução do Exercício 5.6.5 O primeiro passo consiste na dedução das fórmulas que re-
lacionam a largura e e o ângulo inicial ψ0 com o raio r e com o número nφ de treliças
pretendidas.
402 SOLUÇÕES
Para isso, através da observação da figura anterior é fácil constatar que r0 sin ψ0 =
r cos α. Sendo e = 2r sin( α2 ) e α = n2πφ , temos
π
e = 2r sin
nφ
e ainda
π
r cos nφ
ψ0 = asin
r0
escada_de_mao(ais, bis) =
begin
nos_trelica(ais)
nos_trelica(bis)
barras_trelica(ais, bis)
barras_trelica(ais, ais[2:end])
barras_trelica(bis, bis[2:end])
end
coordenadas_linha_aleatoria(p, l, n, r) =
if n == 0
[p+vxyz_aleatorio(r)]
else
[p+vxyz_aleatorio(r), coordenadas_linha_aleatoria(p+vx(l), l, n-1, r)...]
end
vxyz_aleatorio(r) =
vxyz(random_range(-r, +r), random_range(-r, +r), random_range(-r, +r))
trelica_ondulada(p, rac, rb, l, n, fi, psi0, psi1, alfa0, alfa1, d_alfa, d_r) =
trelica_espacial(
coordenadas_trelica_ondulada(
p, rac, rb, l,
fi, psi0, psi1, (psi1 - psi0)/n,
alfa0, alfa1, d_alfa, d_r))
banheira(p, r, e, l) =
let re = r + e
subtraction(box(p - vxyz(re, re, re),
p + vxyz(l + re, re, 0)),
union(sphere(p, r),
cylinder(p, r, p + vx(l)),
sphere(p + vx(l), r)))
end
R ∪ RC = U
R ∩ RC = ∅
∅C = U
UC = ∅
R0 ∩ R1C = R0 \ R1
R0C ∪ R1 = (R0 \ R1 )C
is_complemento(objecto) =
isinstance(objecto, tuple) && objecto[1] is "-"
complemento(regiao) =
if is_empty_shape(regiao)
universal_shape()
elseif is_universal_shape(regiao)
empty_shape()
elseif is_complemento(regiao)
regiao[2:end]
else
"-", regiao
end
405
uniao(r0, r1) =
if is_complemento(r0) && is_complemento(r1)
complemento(intersection(complemento(r0), complemento(r1)))
elseif is_complemento(r0)
complemento(subtraccao(complemento(r0), r1))
elseif is_complemento(r1)
complemento(subtraccao(r0, complemento(r1)))
else
union(r0, r1)
end
interseccao(r0, r1) =
if is_complemento(r0) && is_complemento(r1)
complemento(uniao(complemento(r0), complemento(r1)))
elseif is_complemento(r0)
subtraccao(r1, complemento(r0))
elseif is_complemento(r1)
subtraccao(r0, complemento(r1))
else
intersection(r0, r1)
end
subtraccao(r0, r1) =
if is_complemento(r0) && is_complemento(r1)
subtraction(complemento(r1), complemento(r0))
elseif is_complemento(r0)
complemento(uniao(complemento(r0), r1))
elseif is_complemento(r1)
complemento(uniao(r0, complemento(r1)))
else
subtraction(r0, r1)
end
Solução do Exercício 6.4.5 Uma vez que toda a geometria da secção se define a partir da
largura l, é necessário deduzir os restantes parâmetros a partir de l. O seguinte esquema
mostra as relações entre os vários parâmetros.
406 SOLUÇÕES
d
r1
r0
α
seccao_petronas(p, l) =
let d = l/sqrt(2),
cos_a = cos(pi/8),
r0 = l/2*cos_a,
r1 = l*(cos_a/sqrt(2)-1/2*cos_a)
union(surface_regular_polygon(4, p, d, 0),
surface_regular_polygon(4, p, d, pi/4),
uniao_circulos(p, r0, pi/8, pi/4, r1, 8))
end
407
tronco_cone_petronas(p, r, dr, h, n) =
if n == 0
p
else
seccao_petronas(p, r)
tronco_cone_petronas(p+vz(h), r+dr, dr, h, n-1)
end
torre_petronas(p) =
let p = bloco_petronas(p, 37, 37, 37, 5, 6),
p = bloco_petronas(p, 35, 35, 36, 4, 52),
p = bloco_petronas(p, 32, 32, 33, 4, 12),
p = bloco_petronas(p, 28, 27, 28, 4, 8),
p = bloco_petronas(p, 24, 21, 22, 4, 5),
p = bloco_petronas(p, 18, 15, 16, 4, 4),
p = bloco_petronas(p, 14, 5, 4, 3, 5)
end
torre_petronas(xy(0, 0))
torre_petronas(xy(96, 0))
malha_tubos(p, c, r, n, m) =
if m == 0
empty_shape()
else
union(linha_tubos(p, c, r, n),
malha_tubos(p+vz(2*r), c, r, n, m-1))
end
linha_tubos(p, c, r, n) =
if n == 0
empty_shape()
else
union(subtraction(cylinder(p, r, p+vy(c)),
cylinder(p, 0.9*r, p+vy(c))),
linha_tubos(p+vx(2*r), c, r, n-1))
end
cobertura_tubos(p, h, n) =
let r1 = h/2.0/n,
r0 = h-r1
subtraction(malha_tubos(p+vxyz(-r0, 0, r1), h, r1, 2*n, n),
sphere(p, r0))
end
cobertura_tubos(xyz(0, 0, 0), 5, 9)
408 SOLUÇÕES
Solução do Exercício 6.4.10 Uma vez que se pretende uma “esfera,” o melhor será usar
coordenadas esféricas para os centros das bases dos cones.
[cone(sph(1, pi*fi, pi*psi), 0.1*sin(pi*psi), u0())
for fi in 0:1/10:2
for psi in 1/30:7/75:1]
casca_esferica_perfurada(p, r, e, rc, n) =
subtraction([sphere(p, r),
sphere(p, r-e),
[cone(p+vsph(1.1*r, i*2*pi/n, pi*psi), 0.1, p)
for psi in 0:1/10:1
for i in 0:1:floor(15*sin(pi*psi))+1]...])
coordenada_aleatoria(p0, p1) =
xyz(random_range(cx(p0), cx(p1)),
random_range(cy(p0), cy(p1)),
random_range(cz(p0), cz(p1)))
lista_coordenadas_aleatorias(p0, p1, n) =
[coordenada_aleatoria(p0, p1) for i in 1:n]
lista_valores_aleatorios(a, b, n) =
[random_range(a, b) for i in 1:n]
uniao_esferas(cs, rs, e) =
union([sphere(c, r-e) for (c, r) in zip(cs, rs)])
cubo_furado(p, lado) =
for i in 0:2
for j in 0:2
for k in 0:2
if !(i == j == 1 || i == k == 1 || j == k == 1)
cubo_vertice(p+vxyz(i*lado, j*lado, k*lado), lado)
end
end
end
end
cubo_vertice(p, lado) =
box(p, p+vxyz(lado, lado, lado))
410 SOLUÇÕES
cobertura_arcos_romanos(p, r, e, n) =
let da = 2*pi/n,
lc = r*cos(da/2),
rc = r*sin(da/2)
subtraction(raios_cilindro(p, lc, 0, da, rc, n),
raios_cilindro(p, r, 0, da, rc-e, n),
cylinder(p, r, p+vz(-rc)))
end
octaedro_estrelado(p, l) =
union(tetraedro(p+vxyz(l/-2, l/-2, l/-2),
p+vxyz(l/2, l/2, l/-2),
p+vxyz(l/-2, l/2, l/2),
p+vxyz(l/2, l/-2, l/2)),
tetraedro(p+vxyz(l/2, l/-2, l/-2),
p+vxyz(l/-2, l/2, l/-2),
p+vxyz(l/-2, l/-2, l/2),
p+vxyz(l/2, l/2, l/2)))
411
meio_xyz(p0, p1) =
xyz(meio(p0.x, p1.x), meio(p0.y, p1.y), meio(p0.z, p1.z))
Solução do Exercício 6.6.9 As imagens foram geradas com d_fi a variar ao longo da
sequência [0, π, π/2, π/4].
Solução do Exercício 6.6.10
corrimao(p, a, omega, fi, lx, dx, l_corrimao, a_corrimao) =
sweep(spline(pontos_sinusoide(p, a, omega, fi, 0, lx, dx)),
rectangle(xy(-l_corrimao/2.0, -a_corrimao/2.0), l_corrimao, a_corrimao))
Solução do Exercício 6.8.2 Para que os arcos de circunferência possam interligar-se sua-
vemente, é necessário que exista uma relação entre os raios r0 , r1 e r2 . É facil ver que
(r2 − r1 ) cos α + r0 = r2
Por outro lado, a altura h do óvulo é dada por
h = r0 + (r2 − r1 ) sin α + r1
Uma vez que a função não recebe nem o raio r2 nem o ângulo α, temos de os deduzir. Para
isso, vamos resolver a primeira equação em ordem a r2
r0 − r1 cos α
r2 =
1 − cos α
e vamos substituir na segunda equação
r0 − r1 cos α
h = r0 + ( − r1 ) sin α + r1
1 − cos α
Simplificando, obtemos
sin α
h = r0 + r1 + (r0 − r1 )
1 − cos α
Empregando agora a igualdade trigonométrica
α 1 − cos α
tan =
2 sin α
temos
1
h = r0 + r1 + (r0 − r1 ) α
tan 2
ou seja,
r0 − r1
α = 2 tan−1
h − r0 − r1
Estamos agora em condições de definir a função pretendida:
ovo(p, r0, r1, h) =
let alfa = 2*atan2(r0-r1, h-r0-r1),
r2 = (r0-r1*cos(alfa))/(1-cos(alfa))
revolve(union(arc(p, r0, -pi/2, pi/2),
arc(p+vx(r0-r2), r2, 0, alfa),
arc(p+vy((r2-r1)*sin(alfa)), r1, alfa, (pi-alfa-alfa)/2)),
p,
vy())
end
414 SOLUÇÕES
varanda(p, f, x0, x1, dx, ly, a_laje, a_guarda, l_corrimao, a_corrimao, d_prumos) =
begin
laje(p, f, x0, x1, d_prumos, ly, a_laje)
guarda(p + vxyz(0, l_corrimao, a_laje), f, x0, x1, dx, ly, a_guarda, l_corrimao, a_corrimao, d_prumos)
end
416 SOLUÇÕES
serra(a, b, c, x) =
abs(a + b*(x - c))
varanda(xyz(0, 0, 0),
x -> serra(0, 0.3, 3*pi, x),
0, 4*pi, 0.5,
4, 0.2, 1, 0.04, 0.02, 0.4)
varanda(xyz(8, 8, 0),
x -> oscilatorio_amortecido(-2, 0.1, 2, x),
0, 4*pi, 0.2,
4, 0.2, 1, 0.04, 0.02, 0.2)
varanda(xyz(16, 16, 0),
x -> sinusoide_limitada(1.0, 1.0, 0, x),
0, 4*pi, 0.2,
4, 0.2, 1, 0.04, 0.02, 0.4)
roundv(v) =
vxyz(round(v.x), round(v.y), round(v.z))
caminho_de_tubos(p0, v0, l, n) =
if n == 0
nothing
else
let v1 = roundv(vsph(1, random_range(0, 4)*pi/2, random_range(0, 3)*pi/2))
if v0 == -v1
caminho_de_tubos(p0, v0, l, n)
else
let p1 = p0 + v1*random_range(0.2, 1.0)*l
cylinder(p0, 0.02*l, p1)
sphere(p1, 0.04*l)
caminho_de_tubos(p1, v1, l, n - 1)
end
end
end
end
417
somatorio(f, a, b) =
acumulatorio((a, b) -> a + b, f, 0, a, i -> i + 1, b)
produtorio(f, a, b) =
acumulatorio((a, b) -> a*b, f, 1, a, i -> i + 1, b)
julia> aproxima_pi(2000)
3.140592653839793
418 SOLUÇÕES
cria_edificios(pontos, poligonos) =
for poligono in poligonos
let pts = pontos_no_poligono(pontos, poligono),
n_pts = length(pts)
if n_pts == 0
nothing
elseif n_pts == 1
cria_edificio(poligono, pts[1].z)
else
cria_edificio(poligono, media(mapeia(cz, pts)))
end
end
end
tanque_sergels(p) =
begin
tanque_superelipse(p, 0.4, 1.0, 22, 22, 0.7, 100)
tanques_circulares(p, 1.0, 0.2, 0.8, 17, 17, 0.7, 8)
tanques_circulares(p, 1.0, 0.2, 0.8, 12, 12, 0.7, 12)
end
cobertura_ysios(
curva_ysios(xyz(0, -20, 5.0), 0.0, 1.0, -4.0, 1.0),
curva_ysios(xyz(0, -20, 5.7), 0.0, 1.0, -6.0, 2.8),
curva_ysios(xyz(0, 20, 5.0), 0.0, -1.0, 0.0, -1.0),
curva_ysios(xyz(0, 20, 5.7), 0.0, -1.0, 0.0, -1.0))
0 ≤ u ≤ 2π; 0 ≤ v ≤ 2π;
A definição da função fica então:
itera_quadrangulos(trama_tecido_quad, malha_teste())
oscila_curvas(ptss, r) =
[oscila_faixa(ptss[1], ptss[2], r),
(length(ptss) == 2 ?
[] :
oscila_curvas(ptss[2:end], -r))...]
oscila_faixa(pts0, pts1, r) =
[oscila_centro(pts0[1], pts1[1], pts1[2], pts0[2], r),
(length(pts0) == 2 ?
[] :
oscila_faixa(pts0[2:end], pts1[2:end], -r))...]
tecido(malha) =
let p0 = malha[1][1],
p1 = malha[2][1],
p2 = malha[2][2],
p3 = malha[1][2:end][1],
d = min(distance(p0, p1), distance(p0, p3)),
r = d/6.0
map(curva -> sweep(spline(curva),
surface(circle(xyz(0, 0, 0), r))),
[oscila_curvas(malha, r)...,
oscila_curvas(matriz_transposta(malha), r)...])
end
Solução do Exercício 9.11.4 O “defeito” é a falta de uma fileira de barras na parte de baixo
da treliça. A explicação está no facto de a treliça ser, na realidade, uma fita comprida em
que extremidades estão coincidentes. A falta da fileira de barras é, na verdade, o início e
fim da fita.
425
ourico(p, r, a, h, f, n, n_agulhas) =
begin
surface_grid(pontos_ourico(p, r, a, h, f, n))
with(current_layer, create_layer("Black")) do
itera_quadrangulos(agulha_quad, pontos_ourico(p, r, a, h, f, n_agulhas))
end
end
426 SOLUÇÕES
Índice
*, 11 colunas_doricas_entre, 105
<, 21 colunas_tholos, 110
<=, 21, 383 composicao, 290
=, 383 cone, 60
==, 21 cone_frustum, 63
>, 21, 157, 383 coordenadas_trelica_aleatoria, 188
>=, 21 coordenadas_trelica_horizontal, 188
^, 17, 20, 289 coordenadas_x, 177
copy_shape, 255
corda, 345
abaco, 78 corrimao, 228
abobada_trelicas, 182 cos, 17
abs, 17, 20 cosh, 28
acosh, 28 cross, 218, 363
acumulatorio, 288 cruzeta, 62
aleatorio, 133–136 cubo, 89
all_shapes, 295 cubo_de_tubos, 286
ampulheta, 63 cupula_revolucao, 242
aproximadamente_colineares, 330 curva_ysios, 357, 358
arcadas_folio, 248 cx, 33
arco_espiral, 120 cy, 33
arco_falso, 98 cz, 33
area_triangulo, 15, 26, 27
arvore, 123, 126, 138, 140 distancia, 32
asinh, 28 divide_poligono, 165
atan, 17 divisao_aleatoria_poligono, 166
atanh, 28 division, 310, 311
dobro, 14
bacia, 196 dot, 218
backend, 41
banheira, 197 elementos_aleatorios, 156
barra_trelica, 174, 175 elimina, 158
barras_trelica, 174, 178, 179, 183 elimina1, 158
barril, 249 elipsoide, 351
bisseccao_aleatoria_poligono, 166 empty_shape, 202, 206
bisseccao_poligono, 166 end, 26, 27
blocos_conexos, 142 enumera, 157, 158, 288, 306, 308, 309
box, 60 enumera_n, 310
breather, 346 equilibrio_circulos, 98
escada, 75, 94, 95
calota_esferica, 220 escada_de_mao, 183
caminhada_aleatoria, 141 escada_progressao_geometrica, 102
caminho_de_tubos, 285, 286 escada_rampa, 102
cara_ou_coroa, 142 escadas, 75
ceil, 17 esfera_de_tubos, 286
cilindro_de_tubos, 286 esferas_na_esfera, 151
cilindros_aleatorios, 141 espiral, 115, 116
cilindros_esfera, 142 espiral_arquimedes, 311
cilindros_espiral_arquimedes, 312 esponja_menger, 215
circle, 41, 198, 296 exp, 17
circle_center, 295, 296 extrusion, 222, 233
circle_radius, 295
circulo_e_raio, 48 f1, 280
circulo_interior_folio, 201, 203, 204 f2, 280
circulo_quadrado, 48 factorial, 92, 133
circulos, 100 false, 20
circulos_radiais, 99, 390 filter, 292, 293
closed_line, 167 filtra, 292
closed_spline, 167 floor, 17
cobertura_arcos_romanos, 215 flor, 100, 390
cobertura_ysios, 357 folha, 140
coluna, 51, 55, 78 folhas_folio, 201, 202
colunas_doricas, 105 forma_de_tubos, 286
427
428 ÍNDICE