Programming for Architecture
1 Preface
2 Introduction
2.1 Programming Languages
2.1.1 Exercises 1
2.1.1.1 Question 1
2.1.1.2 Question 2
2.1.1.3 Question 3
2.2 The Racket Language
2.2.1 Syntax, Semantics and Pragmatics
2.2.2 Syntax and Semantics of Racket
2.2.3 The Evaluator
2.3 Language Elements
2.3.1 Numbers
2.4 Combinations
2.4.1 Indentation
2.4.2 Exercises 2
2.4.2.1 Question 4
2.4.2.2 Question 5
2.4.2.3 Question 6
2.4.2.4 Question 7
2.4.2.5 Question 8
2.4.2.6 Question 9
2.4.3 Evaluating Combinations
2.4.3.1 Question 10
2.4.4 Strings
2.5 Defining Functions
2.5.1 Exercises 3
2.5.1.1 Question 11
2.6 Names
2.6.1 Exercises 4
2.6.1.1 Question 12
2.6.1.2 Question 13
2.6.1.3 Question 14
2.6.1.4 Question 15
2.6.1.5 Question 16
2.6.1.6 Question 17
2.6.1.7 Question 18
2.7 Predefined Functions
2.7.1 Exercises 5
2.7.1.1 Question 19
2.7.1.2 Question 20
2.7.1.3 Question 21
2.7.1.4 Question 22
2.7.1.5 Question 23
2.8 Arithmetic in Racket
2.8.1 Exercises 6
2.8.1.1 Question 24
2.8.1.2 Question 25
2.8.1.3 Question 26
2.9 Name Evaluation
2.10 Conditional Expressions
2.10.1 Logical Expressions
2.10.2 Logical Values
2.11 Predicates
2.11.1 Arithmetic Predicates
2.12 Logical Operators
2.12.1 Exercises 7
2.12.1.1 Question 27
2.13 Predicates with a Variable Number of Arguments
2.14 Recognizers
2.14.1 Exercises 8
2.14.1.1 Question 28
2.14.1.2 Question 29
2.14.1.3 Question 30
2.14.1.4 Question 31
2.14.1.5 Question 32
2.14.1.6 Question 33
2.14.1.7 Question 34
2.15 Selection
2.16 Multiple Selection
2.16.1 Exercises 9
2.16.1.1 Question 35
2.16.1.2 Question 36
2.16.1.3 Question 37
2.17 Local Variables
2.18 Global Variables
2.19 Modules
2.19.1 Exercises 10
2.19.1.1 Question 38
2.19.1.2 Question 39
2.19.1.3 Question 40
3 Modelling
3.1 Coordinates
3.2 Operations with Coordinates
3.2.1 Exercises 11
3.2.1.1 Question 41
3.2.1.2 Question 42
3.2.2 Bi-dimensional Coordinates
3.2.3 Exercises 12
3.2.3.1 Question 43
3.2.3.2 Question 44
3.2.4 Polar Coordinates
3.2.5 Exercises 13
3.2.5.1 Question 45
3.3 Bi-dimensional Geometric Modelling
3.3.1 Exercises 14
3.3.1.1 Question 46
3.3.1.2 Question 47
3.3.1.3 Question 48
3.3.1.4 Question 49
3.4 Side Effects
3.5 Sequencing
3.5.1 Exercises 15
3.5.1.1 Question 50
3.5.1.2 Question 51
3.6 Doric Order
3.7 Parametrization of Geometric Figures
3.8 Documentation
3.8.1 Exercises 16
3.8.1.1 Question 52
3.8.1.2 Question 53
3.8.1.3 Question 54
3.8.1.4 Question 55
3.9 Debugging
3.9.1 Syntactic Errors
3.9.2 Semantic Errors
3.10 Three-dimensional Modelling
3.10.1 Predefined Solids
3.10.2 Exercises 17
3.10.2.1 Question 56
3.10.2.2 Question 57
3.10.3 Exercises 18
3.10.3.1 Question 58
3.10.3.2 Question 59
3.10.3.3 Question 60
3.10.3.4 Question 61
3.10.3.5 Question 62
3.11 Cylindrical Coordinates
3.11.1 Exercises 19
3.11.1.1 Question 63
3.11.1.2 Question 64
3.12 Spherical Coordinates
3.12.1 Exercises 20
3.12.1.1 Question 65
3.12.1.2 Question 66
3.13 Modelling Doric Columns
3.13.1 Exercises 21
3.13.1.1 Question 67
3.14 Vitruvian Proportions
3.14.1 Exercises 22
3.14.1.1 Question 68
4 Recursion
4.1 Introduction
4.1.1 Exercises 23
4.1.1.1 Question 69
4.1.1.2 Question 70
4.2 Recursion in Architecture
4.2.1 Exercises 24
4.2.1.1 Question 71
4.2.1.2 Question 72
4.2.1.3 Question 73
4.2.1.4 Question 74
4.2.1.5 Question 75
4.3 Debugging Recursive Programs
4.3.1 Exercises 25
4.3.1.1 Question 76
4.3.1.2 Question 77
4.3.1.3 Question 78
4.3.1.4 Question 79
4.3.1.5 Question 80
4.3.1.6 Question 81
4.4 Doric Temples
4.4.1 Exercises 26
4.4.1.1 Question 82
4.4.1.2 Question 83
4.4.1.3 Question 84
4.4.1.4 Question 85
4.4.1.5 Question 86
4.4.1.6 Question 87
4.4.1.7 Question 88
4.4.1.8 Question 89
4.5 Ionic Order
4.5.1 Exercises 27
4.5.1.1 Question 90
4.5.1.2 Question 91
4.5.1.3 Question 92
4.6 Recursion in Nature
5 State
5.1 Introduction
5.2 Randomness
5.3 Random Numbers
5.4 State
5.5 Random Choices
5.5.1 Random Fractional Numbers
5.5.2 Random Numbers within a Range
5.5.3 Exercises 28
5.5.3.1 Question 93
5.5.3.2 Question 94
5.5.3.3 Question 95
5.5.3.4 Question 96
5.5.3.5 Question 97
5.5.3.6 Question 98
5.6 Urban Planning
5.6.1 Exercises 29
5.6.1.1 Question 99
5.6.1.2 Question 100
5.6.1.3 Question 101
5.6.1.4 Question 102
5.6.1.5 Question 103
5.6.1.6 Question 104
6 Structures
6.1 Introduction
6.2 Lists
6.2.1 Pairs
6.2.2 Graphic Representation of Pairs
6.3 Recursive Types
6.4 Recursion in Lists
6.4.1 Exercises 30
6.4.1.1 Question 105
6.4.1.2 Question 106
6.4.1.3 Question 107
6.4.1.4 Question 108
6.4.1.5 Question 109
6.4.1.6 Question 110
6.4.1.7 Question 111
6.4.1.8 Question 112
6.5 Predicates on Lists
6.5.1 Exercises 31
6.5.1.1 Question 113
6.5.1.2 Question 114
6.5.1.3 Question 115
6.5.1.4 Question 116
6.5.1.5 Question 117
6.6 Enumerations
6.6.1 Exercises 32
6.6.1.1 Question 118
6.6.1.2 Question 119
6.6.1.3 Question 120
6.6.1.4 Question 121
6.6.1.5 Question 122
6.6.1.6 Question 123
6.6.1.7 Question 124
6.6.1.8 Question 125
6.6.1.9 Question 126
6.6.1.10 Question 127
6.7 Polygon
6.7.1 Regular Stars
6.7.2 Regular Polygons
6.7.3 Exercises 33
6.7.3.1 Question 128
6.7.3.2 Question 129
6.7.3.3 Question 130
6.7.3.4 Question 131
6.8 Polygonal Lines and Splines
6.8.1 Exercises 34
6.8.1.1 Question 132
6.8.1.2 Question 133
6.9 Trusses
6.9.1 Drawing of Trusses
6.9.1.1 Question 134
6.9.1.2 Question 135
6.9.1.3 Question 136
6.9.1.4 Question 137
6.9.2 Creating Positions
6.9.2.1 Question 140
6.9.2.2 Question 138
6.9.2.3 Question 139
6.9.3 Spatial Trusses
6.9.3.1 Question 141
6.9.4 Exercises 35
6.9.4.1 Question 142
6.9.4.2 Question 143
6.9.4.3 Question 144
7 Constructive Solid Geometry
7.1 Introduction
7.2 Constructive Geometry
7.2.1 Exercises 36
7.2.1.1 Question 145
7.2.1.2 Question 146
7.3 Surfaces
7.3.1 Trefoils, Quatrefoils and Other Foils
7.4 Algebra of Shapes
7.4.1 Exercises 37
7.4.1.1 Question 147
7.4.1.2 Question 148
7.4.1.3 Question 149
7.4.1.4 Question 150
7.4.1.5 Question 151
7.4.1.6 Question 152
7.4.1.7 Question 153
7.4.1.8 Question 154
7.4.1.9 Question 155
7.4.1.10 Question 156
7.4.1.11 Question 157
7.4.1.12 Question 158
7.4.1.13 Question 159
7.4.1.14 Question 160
7.4.1.15 Question 161
7.4.1.16 Question 162
7.4.1.17 Question 163
7.5 Slice of Regions
7.5.1 Exercises 38
7.5.1.1 Question 164
7.5.1.2 Question 165
7.5.1.3 Question 166
7.6 Extrusions
7.6.1 Simple Extrusion
7.6.1.1 Question 167
7.6.1.2 Question 168
7.6.1.3 Question 169
7.6.1.4 Question 170
7.6.1.5 Question 171
7.6.1.6 Question 172
7.6.1.7 Question 173
7.6.1.8 Question 174
7.6.1.9 Question 175
7.6.1.10 Question 176
7.6.1.11 Question 177
7.6.2 Extrusion Along a Path
7.6.2.1 Question 178
7.6.2.2 Question 179
7.6.3 Extrusion with Transformation
7.7 Gaudí’s Columns
7.8 Revolutions
7.8.1 Surfaces of Revolution
7.8.1.1 Question 180
7.8.1.2 Question 181
7.8.2 Solids of Revolution
7.8.2.1 Question 182
7.8.2.2 Question 183
7.8.2.3 Question 184
7.8.2.4 Question 185
7.9 Sections Interpolation
7.9.1 Interpolation by Sections
7.9.2 Interpolation with Guiding
7.9.2.1 Question 186
8 Transformations
8.1 Introduction
8.2 Translation
8.3 Scale
8.4 Rotation
8.5 Reflection
8.6 The Sydney Opera House
8.6.1 Exercises 39
8.6.1.1 Question 187
8.6.1.2 Question 188
8.6.1.3 Question 189
9 Higher-Order Functions
9.1 Introduction
9.2 Curvy Facades
9.3 Higher-Order Functions
9.4 Anonymous Functions
9.4.1 Exercises 40
9.4.1.1 Question 190
9.4.1.2 Question 191
9.4.1.3 Question 192
9.4.1.4 Question 193
9.5 Identity Function
9.5.1 Exercises 41
9.5.1.1 Question 194
9.5.1.2 Question 195
9.5.1.3 Question 196
9.5.1.4 Question 197
9.6 The Function Restriction
9.6.1 Exercises 42
9.6.1.1 Question 198
9.6.1.2 Question 199
9.6.2 Exercises 43
9.6.2.1 Question 200
9.7 The Composition Function
9.7.1 Exercises 44
9.7.1.1 Question 201
9.7.1.2 Question 202
9.7.1.3 Question 203
9.8 Higher Order Functions on Lists
9.8.1 Mapping
9.8.2 Filtering
9.8.3 Reduction
9.8.4 Exercises 45
9.8.4.1 Question 204
9.9 Generation of Three-Dimensional Models
9.9.1 Exercises 46
9.9.1.1 Question 205
9.9.1.2 Question 206
9.9.2 Exercises 47
9.9.2.1 Question 207
10 Parametric Representation
10.1 Introduction
10.2 Computation of Parametric Functions
10.3 Rounding errors
10.4 Mapping and Enumerations
10.4.1 Exercises 48
10.4.1.1 Question 208
10.4.1.2 Question 209
10.4.1.3 Question 210
10.4.2 Fermat’s Spiral
10.4.3 Cissoid of Diocles
10.4.4 Lemniscate of Bernoulli
10.4.5 Exercises 49
10.4.5.1 Question 211
10.4.6 Lamé Curve
10.4.7 Exercises 50
10.4.7.1 Question 212
10.4.7.2 Question 213
10.4.7.3 Question 214
10.4.7.4 Question 215
10.4.7.5 Question 216
10.4.7.6 Question 217
10.5 Precision
10.5.1 Adaptive Sampling
10.5.2 Exercises 51
10.5.2.1 Question 218
10.5.2.2 Question 219
10.6 Parametric Surfaces
10.6.1 The Möbius Strip
10.7 Surfaces
10.7.1 Exercises 52
10.7.1.1 Question 220
10.7.1.2 Question 221
10.7.2 Helicoid
10.7.3 Spring
10.7.4 Exercises 53
10.7.4.1 Question 222
10.7.4.2 Question 223
10.7.5 Shells
10.7.6 Cylinders, Cones, and Spheres
10.7.7 Exercises 54
10.7.7.1 Question 224
10.8 Bodegas Ysios
10.8.1 Exercises 55
10.8.1.1 Question 225
10.8.1.2 Question 226
10.8.1.3 Question 227
10.8.1.4 Question 228
10.8.1.5 Question 229
10.8.1.6 Question 230
10.8.1.7 Question 231
10.8.1.8 Question 232
10.9 Surface Normals
10.10 Surface Processing
10.10.1 Exercises 56
10.10.1.1 Question 233
10.10.1.2 Question 234
10.10.1.3 Question 235
10.10.2 Exercises 57
10.10.2.1 Question 236
10.10.2.2 Question 237
11 Epilogue
12 Operations
12.1 Types
Real
Integer
Loc
Vec
Cs
Shape
12.2 Coordinate Space
world-cs
current-cs
with-cs
12.3 Locations
xyz
cx
cy
cz
x
y
z
xy
xz
yz
+  xyz
+  x
+  y
+  z
+  xy
+  xz
+  yz
pol
pol-rho
pol-phi
+  pol
cyl
+  cyl
sph
+  sph
12.4 Vectors
vxyz
cx
cy
cz
vx
vy
vz
vxy
vxz
vyz
-vx
-vy
-vz
+  vxyz
+  vx
+  vy
+  vz
+  vxy
+  vxz
+  vyz
vpol
pol-rho
pol-phi
+  vpol
vcyl
+  vcyl
vsph
+  vsph
u-vxyz
unitize
12.5 Operations with Locations
distance
12.6 Algebraic Operations with Locations and Vectors
p-p
p+  v
v+  v
v*r
v/  r
u0
12.7 1D Modeling
point
12.8 2D Modeling
circle
surface-circle
arc
surface-arc
ellipse
line
closed-line
polygon
surface-polygon
spline
closed-spline
surface-polygon
rectangle
rectangle
surface-rectangle
surface-rectangle
regular-polygon
regular-polygon
text
text-centered
12.9 3D Modeling
box
box
cone
cone
cone-frustum
cone-frustum
cylinder
cylinder
cuboid
irregular-pyramid
regular-pyramid
regular-pyramid
regular-pyramid-frustum
regular-pyramid-frustum
regular-prism
regular-prism
right-cuboid
right-cuboid
sphere
pi
-pi
2pi
-2pi
3pi
-3pi
4pi
-4pi
pi/  2
-pi/  2
pi/  3
-pi/  3
pi/  4
-pi/  4
pi/  5
-pi/  5
pi/  6
-pi/  6
3pi/  2
-3pi/  2
12.10 Randomness
random
random-range
Index
6.3

Programming for Architecture

António Menezes Leitão

    1 Preface

    2 Introduction

      2.1 Programming Languages

        2.1.1 Exercises 1

          2.1.1.1 Question 1

          2.1.1.2 Question 2

          2.1.1.3 Question 3

      2.2 The Racket Language

        2.2.1 Syntax, Semantics and Pragmatics

        2.2.2 Syntax and Semantics of Racket

        2.2.3 The Evaluator

      2.3 Language Elements

        2.3.1 Numbers

      2.4 Combinations

        2.4.1 Indentation

        2.4.2 Exercises 2

          2.4.2.1 Question 4

          2.4.2.2 Question 5

          2.4.2.3 Question 6

          2.4.2.4 Question 7

          2.4.2.5 Question 8

          2.4.2.6 Question 9

        2.4.3 Evaluating Combinations

          2.4.3.1 Question 10

        2.4.4 Strings

      2.5 Defining Functions

        2.5.1 Exercises 3

          2.5.1.1 Question 11

      2.6 Names

        2.6.1 Exercises 4

          2.6.1.1 Question 12

          2.6.1.2 Question 13

          2.6.1.3 Question 14

          2.6.1.4 Question 15

          2.6.1.5 Question 16

          2.6.1.6 Question 17

          2.6.1.7 Question 18

      2.7 Predefined Functions

        2.7.1 Exercises 5

          2.7.1.1 Question 19

          2.7.1.2 Question 20

          2.7.1.3 Question 21

          2.7.1.4 Question 22

          2.7.1.5 Question 23

      2.8 Arithmetic in Racket

        2.8.1 Exercises 6

          2.8.1.1 Question 24

          2.8.1.2 Question 25

          2.8.1.3 Question 26

      2.9 Name Evaluation

      2.10 Conditional Expressions

        2.10.1 Logical Expressions

        2.10.2 Logical Values

      2.11 Predicates

        2.11.1 Arithmetic Predicates

      2.12 Logical Operators

        2.12.1 Exercises 7

          2.12.1.1 Question 27

      2.13 Predicates with a Variable Number of Arguments

      2.14 Recognizers

        2.14.1 Exercises 8

          2.14.1.1 Question 28

          2.14.1.2 Question 29

          2.14.1.3 Question 30

          2.14.1.4 Question 31

          2.14.1.5 Question 32

          2.14.1.6 Question 33

          2.14.1.7 Question 34

      2.15 Selection

      2.16 Multiple Selection

        2.16.1 Exercises 9

          2.16.1.1 Question 35

          2.16.1.2 Question 36

          2.16.1.3 Question 37

      2.17 Local Variables

      2.18 Global Variables

      2.19 Modules

        2.19.1 Exercises 10

          2.19.1.1 Question 38

          2.19.1.2 Question 39

          2.19.1.3 Question 40

    3 Modelling

      3.1 Coordinates

      3.2 Operations with Coordinates

        3.2.1 Exercises 11

          3.2.1.1 Question 41

          3.2.1.2 Question 42

        3.2.2 Bi-dimensional Coordinates

        3.2.3 Exercises 12

          3.2.3.1 Question 43

          3.2.3.2 Question 44

        3.2.4 Polar Coordinates

        3.2.5 Exercises 13

          3.2.5.1 Question 45

      3.3 Bi-dimensional Geometric Modelling

        3.3.1 Exercises 14

          3.3.1.1 Question 46

          3.3.1.2 Question 47

          3.3.1.3 Question 48

          3.3.1.4 Question 49

      3.4 Side Effects

      3.5 Sequencing

        3.5.1 Exercises 15

          3.5.1.1 Question 50

          3.5.1.2 Question 51

      3.6 Doric Order

      3.7 Parametrization of Geometric Figures

      3.8 Documentation

        3.8.1 Exercises 16

          3.8.1.1 Question 52

          3.8.1.2 Question 53

          3.8.1.3 Question 54

          3.8.1.4 Question 55

      3.9 Debugging

        3.9.1 Syntactic Errors

        3.9.2 Semantic Errors

      3.10 Three-dimensional Modelling

        3.10.1 Predefined Solids

        3.10.2 Exercises 17

          3.10.2.1 Question 56

          3.10.2.2 Question 57

        3.10.3 Exercises 18

          3.10.3.1 Question 58

          3.10.3.2 Question 59

          3.10.3.3 Question 60

          3.10.3.4 Question 61

          3.10.3.5 Question 62

      3.11 Cylindrical Coordinates

        3.11.1 Exercises 19

          3.11.1.1 Question 63

          3.11.1.2 Question 64

      3.12 Spherical Coordinates

        3.12.1 Exercises 20

          3.12.1.1 Question 65

          3.12.1.2 Question 66

      3.13 Modelling Doric Columns

        3.13.1 Exercises 21

          3.13.1.1 Question 67

      3.14 Vitruvian Proportions

        3.14.1 Exercises 22

          3.14.1.1 Question 68

    4 Recursion

      4.1 Introduction

        4.1.1 Exercises 23

          4.1.1.1 Question 69

          4.1.1.2 Question 70

      4.2 Recursion in Architecture

        4.2.1 Exercises 24

          4.2.1.1 Question 71

          4.2.1.2 Question 72

          4.2.1.3 Question 73

          4.2.1.4 Question 74

          4.2.1.5 Question 75

      4.3 Debugging Recursive Programs

        4.3.1 Exercises 25

          4.3.1.1 Question 76

          4.3.1.2 Question 77

          4.3.1.3 Question 78

          4.3.1.4 Question 79

          4.3.1.5 Question 80

          4.3.1.6 Question 81

      4.4 Doric Temples

        4.4.1 Exercises 26

          4.4.1.1 Question 82

          4.4.1.2 Question 83

          4.4.1.3 Question 84

          4.4.1.4 Question 85

          4.4.1.5 Question 86

          4.4.1.6 Question 87

          4.4.1.7 Question 88

          4.4.1.8 Question 89

      4.5 Ionic Order

        4.5.1 Exercises 27

          4.5.1.1 Question 90

          4.5.1.2 Question 91

          4.5.1.3 Question 92

      4.6 Recursion in Nature

    5 State

      5.1 Introduction

      5.2 Randomness

      5.3 Random Numbers

      5.4 State

      5.5 Random Choices

        5.5.1 Random Fractional Numbers

        5.5.2 Random Numbers within a Range

        5.5.3 Exercises 28

          5.5.3.1 Question 93

          5.5.3.2 Question 94

          5.5.3.3 Question 95

          5.5.3.4 Question 96

          5.5.3.5 Question 97

          5.5.3.6 Question 98

      5.6 Urban Planning

        5.6.1 Exercises 29

          5.6.1.1 Question 99

          5.6.1.2 Question 100

          5.6.1.3 Question 101

          5.6.1.4 Question 102

          5.6.1.5 Question 103

          5.6.1.6 Question 104

    6 Structures

      6.1 Introduction

      6.2 Lists

        6.2.1 Pairs

        6.2.2 Graphic Representation of Pairs

      6.3 Recursive Types

      6.4 Recursion in Lists

        6.4.1 Exercises 30

          6.4.1.1 Question 105

          6.4.1.2 Question 106

          6.4.1.3 Question 107

          6.4.1.4 Question 108

          6.4.1.5 Question 109

          6.4.1.6 Question 110

          6.4.1.7 Question 111

          6.4.1.8 Question 112

      6.5 Predicates on Lists

        6.5.1 Exercises 31

          6.5.1.1 Question 113

          6.5.1.2 Question 114

          6.5.1.3 Question 115

          6.5.1.4 Question 116

          6.5.1.5 Question 117

      6.6 Enumerations

        6.6.1 Exercises 32

          6.6.1.1 Question 118

          6.6.1.2 Question 119

          6.6.1.3 Question 120

          6.6.1.4 Question 121

          6.6.1.5 Question 122

          6.6.1.6 Question 123

          6.6.1.7 Question 124

          6.6.1.8 Question 125

          6.6.1.9 Question 126

          6.6.1.10 Question 127

      6.7 Polygon

        6.7.1 Regular Stars

        6.7.2 Regular Polygons

        6.7.3 Exercises 33

          6.7.3.1 Question 128

          6.7.3.2 Question 129

          6.7.3.3 Question 130

          6.7.3.4 Question 131

      6.8 Polygonal Lines and Splines

        6.8.1 Exercises 34

          6.8.1.1 Question 132

          6.8.1.2 Question 133

      6.9 Trusses

        6.9.1 Drawing of Trusses

          6.9.1.1 Question 134

          6.9.1.2 Question 135

          6.9.1.3 Question 136

          6.9.1.4 Question 137

        6.9.2 Creating Positions

          6.9.2.1 Question 140

          6.9.2.2 Question 138

          6.9.2.3 Question 139

        6.9.3 Spatial Trusses

          6.9.3.1 Question 141

        6.9.4 Exercises 35

          6.9.4.1 Question 142

          6.9.4.2 Question 143

          6.9.4.3 Question 144

    7 Constructive Solid Geometry

      7.1 Introduction

      7.2 Constructive Geometry

        7.2.1 Exercises 36

          7.2.1.1 Question 145

          7.2.1.2 Question 146

      7.3 Surfaces

        7.3.1 Trefoils, Quatrefoils and Other Foils

      7.4 Algebra of Shapes

        7.4.1 Exercises 37

          7.4.1.1 Question 147

          7.4.1.2 Question 148

          7.4.1.3 Question 149

          7.4.1.4 Question 150

          7.4.1.5 Question 151

          7.4.1.6 Question 152

          7.4.1.7 Question 153

          7.4.1.8 Question 154

          7.4.1.9 Question 155

          7.4.1.10 Question 156

          7.4.1.11 Question 157

          7.4.1.12 Question 158

          7.4.1.13 Question 159

          7.4.1.14 Question 160

          7.4.1.15 Question 161

          7.4.1.16 Question 162

          7.4.1.17 Question 163

      7.5 Slice of Regions

        7.5.1 Exercises 38

          7.5.1.1 Question 164

          7.5.1.2 Question 165

          7.5.1.3 Question 166

      7.6 Extrusions

        7.6.1 Simple Extrusion

          7.6.1.1 Question 167

          7.6.1.2 Question 168

          7.6.1.3 Question 169

          7.6.1.4 Question 170

          7.6.1.5 Question 171

          7.6.1.6 Question 172

          7.6.1.7 Question 173

          7.6.1.8 Question 174

          7.6.1.9 Question 175

          7.6.1.10 Question 176

          7.6.1.11 Question 177

        7.6.2 Extrusion Along a Path

          7.6.2.1 Question 178

          7.6.2.2 Question 179

        7.6.3 Extrusion with Transformation

      7.7 Gaudí’s Columns

      7.8 Revolutions

        7.8.1 Surfaces of Revolution

          7.8.1.1 Question 180

          7.8.1.2 Question 181

        7.8.2 Solids of Revolution

          7.8.2.1 Question 182

          7.8.2.2 Question 183

          7.8.2.3 Question 184

          7.8.2.4 Question 185

      7.9 Sections Interpolation

        7.9.1 Interpolation by Sections

        7.9.2 Interpolation with Guiding

          7.9.2.1 Question 186

    8 Transformations

      8.1 Introduction

      8.2 Translation

      8.3 Scale

      8.4 Rotation

      8.5 Reflection

      8.6 The Sydney Opera House

        8.6.1 Exercises 39

          8.6.1.1 Question 187

          8.6.1.2 Question 188

          8.6.1.3 Question 189

    9 Higher-Order Functions

      9.1 Introduction

      9.2 Curvy Facades

      9.3 Higher-Order Functions

      9.4 Anonymous Functions

        9.4.1 Exercises 40

          9.4.1.1 Question 190

          9.4.1.2 Question 191

          9.4.1.3 Question 192

          9.4.1.4 Question 193

      9.5 Identity Function

        9.5.1 Exercises 41

          9.5.1.1 Question 194

          9.5.1.2 Question 195

          9.5.1.3 Question 196

          9.5.1.4 Question 197

      9.6 The Function Restriction

        9.6.1 Exercises 42

          9.6.1.1 Question 198

          9.6.1.2 Question 199

        9.6.2 Exercises 43

          9.6.2.1 Question 200

      9.7 The Composition Function

        9.7.1 Exercises 44

          9.7.1.1 Question 201

          9.7.1.2 Question 202

          9.7.1.3 Question 203

      9.8 Higher Order Functions on Lists

        9.8.1 Mapping

        9.8.2 Filtering

        9.8.3 Reduction

        9.8.4 Exercises 45

          9.8.4.1 Question 204

      9.9 Generation of Three-Dimensional Models

        9.9.1 Exercises 46

          9.9.1.1 Question 205

          9.9.1.2 Question 206

        9.9.2 Exercises 47

          9.9.2.1 Question 207

    10 Parametric Representation

      10.1 Introduction

      10.2 Computation of Parametric Functions

      10.3 Rounding errors

      10.4 Mapping and Enumerations

        10.4.1 Exercises 48

          10.4.1.1 Question 208

          10.4.1.2 Question 209

          10.4.1.3 Question 210

        10.4.2 Fermat’s Spiral

        10.4.3 Cissoid of Diocles

        10.4.4 Lemniscate of Bernoulli

        10.4.5 Exercises 49

          10.4.5.1 Question 211

        10.4.6 Lamé Curve

        10.4.7 Exercises 50

          10.4.7.1 Question 212

          10.4.7.2 Question 213

          10.4.7.3 Question 214

          10.4.7.4 Question 215

          10.4.7.5 Question 216

          10.4.7.6 Question 217

      10.5 Precision

        10.5.1 Adaptive Sampling

        10.5.2 Exercises 51

          10.5.2.1 Question 218

          10.5.2.2 Question 219

      10.6 Parametric Surfaces

        10.6.1 The Möbius Strip

      10.7 Surfaces

        10.7.1 Exercises 52

          10.7.1.1 Question 220

          10.7.1.2 Question 221

        10.7.2 Helicoid

        10.7.3 Spring

        10.7.4 Exercises 53

          10.7.4.1 Question 222

          10.7.4.2 Question 223

        10.7.5 Shells

        10.7.6 Cylinders, Cones, and Spheres

        10.7.7 Exercises 54

          10.7.7.1 Question 224

      10.8 Bodegas Ysios

        10.8.1 Exercises 55

          10.8.1.1 Question 225

          10.8.1.2 Question 226

          10.8.1.3 Question 227

          10.8.1.4 Question 228

          10.8.1.5 Question 229

          10.8.1.6 Question 230

          10.8.1.7 Question 231

          10.8.1.8 Question 232

      10.9 Surface Normals

      10.10 Surface Processing

        10.10.1 Exercises 56

          10.10.1.1 Question 233

          10.10.1.2 Question 234

          10.10.1.3 Question 235

        10.10.2 Exercises 57

          10.10.2.1 Question 236

          10.10.2.2 Question 237

    11 Epilogue

    12 Operations

      12.1 Types

      12.2 Coordinate Space

      12.3 Locations

      12.4 Vectors

      12.5 Operations with Locations

      12.6 Algebraic Operations with Locations and Vectors

      12.7 1D Modeling

      12.8 2D Modeling

      12.9 3D Modeling

      12.10 Randomness

    Index

1 Preface

This book was born in 2007, after an invitation to teach an introductory programming course to the students of Architecture at Instituto Superior Técnico (IST). The original motivation for the introduction of this course was the same as for several other courses: similar to Mathematics and Physics, Programming is now one of the fundamental courses that constitute the basic education of any IST student.

With this premise, it did not seem to be a subject that would entice the student’s interest, particularly since it was not very clear the contribution it could have to the course. To contradict that first impression, I decided to include in the course’s syllabus some applications of Programming in Architecture. In that sense, I had a conversation with several architectural students and teachers and asked them to explain to me what they did and how they did it. What I heard and saw was revealing.

Despite the enormous progresses that Computer-Aided-Design (CAD) brought to the profession, the truth is that its use continuous to be manual, laborious, repetitive and boring. The creation of a digital model in a CAD tool requires extreme attention to detail, distracting from what is fundamental: the idea. Frequently, the obstacles found end up forcing the Architect to simplify the original idea. Unfortunately, those obstacles do not end with the creation of the model. On the contrary, they become aggravated when the inevitable changes need to be made to the model.

In general, CAD tools are conceived to make the most common tasks easier, in detriment of other less common or sophisticated tasks. In fact, to an Architect interested in modelling more complex shapes, the CAD tool used can raise several limitations. However, those limitations are only deceptions since they can be overcome with the aid of programming. Programming allows a CAD tool to be amplified with new capabilities, thus eliminating the obstacles that restrict the work of the Architect.

The programming practice is intellectually very stimulating but it is also a challenge. It implies the need to master a new language, it implies a new way of thinking. Frequently, that effort makes several people give up but to the ones that prevail in overcoming the initial difficulties, they acquire the skill to go further in the creation of innovative architectural solutions.

This books aims to meet those Architects.

2 Introduction

Knowledge transmission is one of the issues that has worried mankind throughout the ages. As man is able to accumulate knowledge throughout his life, it would be unfortunate if all that knowledge disappeared with his death.

To avoid this loss, mankind invented a series of mechanisms to preserve knowledge. Firstly, oral transmission, consisting in the transmission of knowledge from one person to a small group of people, in a way transferring the problem of knowledge preservation to the next generation. Secondly, written transmission, consisting in documenting knowledge. On one hand, this approach has the great advantage of reaching out to a much larger group of people. On the other hand, it significantly reduces the loss of knowledge due to transmission problems. In fact, the written word allows to preserve knowledge for long periods of time and without the inevitable changes that occur on a long chain of oral transmissions.

It is thanks to the written word that mankind can understand and accumulate vast amounts of knowledge, some of it dating back to thousands of years. Unfortunately, the written word has not always been able to accurately transmit what the author had in mind: the natural language is ambiguous and it evolves with time, making the interpretation of written texts a subjective task. Whether when we write a text or when we read, or interpret one, there are omissions, imprecisions, errors, and ambiguities which can turn the knowledge transmission fallible. If the transmitted knowledge is simple, the receptor will most likely have enough culture and imagination to understand it. For the transmission of more complex knowledge, however,that might be much more difficult to do.

When rigour in the transmission of knowledge is needed, relying on the receptor’s abilities to understand it can have disastrous outcomes and, in fact, throughout history we can find many catastrophic events caused solely by insufficient or incorrect transmission of knowledge.

To avoid these problems, more accurate languages were developed. Mathematics, in particular, has for the past millenia obsessively sought to construct a language that shines for its absolute rigour. This allows knowledge transmission to be much more accurate than in other areas, reducing to the bare minimum the need for imagination in order to understand that knowledge.

To better understand this problem, let us consider one concrete example of knowledge transmission: the calculus of the factorial of a number. If we assume that the person, to whom we want to transmit that knowledge, knows beforehand about numbers and arithmetic operations, we could tell him that, to calculate the factorial of any number, one must multiply every number from one until that number. Unfortunately, that description is too long, and worse yet, inaccurate, because it does not state that only integer numbers are to be multiplied. To avoid these imprecisions and simultaneously make the information more compact, Mathematics developed a set of symbols and concepts that should be understood by everyone. For example, to define the integer sequence of numbers between \(1\) and \(9\), mathematics allows us to write \(1,2,3,\ldots,9\). In the same manner, instead of referring to "any number" mathematics invented the concept of "variable": a name that refers to some "thing" that can be used in several parts of a mathematical statement, always representing the same "thing". That way, Mathematics allows us to more accurately express the factorial computation as follows:

\[n! = 1\times 2\times 3\times \cdots{} \times n\]

But is this definition rigorous enough? Is it possible to interpret it without requiring imagination to figure the author’s intention? Apparently, it is but there is a detail in this definition that requires imagination: the ellipses. The ellipses indicate that the the reader must imagine what should be in its place. Although most readers will correctly understand that the author meant the multiplication of the sequential numbers, some might think to replace the ellipsis with something else.

Even if we exclude this last group of people from our target audience, there are still other problems with the previous definition. Let us imagine, for example, the factorial of \(2\). What is its value? If we use \[n = 2\] in the formula, we get:

\[2! = 1\times 2\times 3\times \cdots{} \times 2\]

In this case, the computation makes no sense, which shows that, in fact, imagination is need for more than just on how to fill in the ellipsis: the number of terms to consider depends on the number to which we want to calculate the factorial.

Assuming that our reader has enough imagination to figure out this particular detail, he would easily calculate that \(2!=1\times 2=2\). But even then, there will be cases where it is not that clear. For example, what is the factorial of zero? The answer does not appear to be obvious. What about the factorial of \(-1\)? Again, it is not clear. And the factorial of \(4.5\)?. Once again the formula says nothing regarding these situations and our imagination can not guess the correct procedure.

Would it be possible to find a way of transmitting the knowledge required to compute the factorial function that minimizes imprecisions, gaps, and ambiguities? Let us try the following variation of the definition of the factorial function:

\[n!= \begin{cases} 1, & \text{if $n=0$}\\ n \cdot (n-1)!, & \text{if $n\in \mathbb{N}$.} \end{cases}\]

Have we reached the necessary rigour that requires no imagination on the reader’s part? One way to find out is with the previous cases that gave us problems. Fist of all, there are no ellipsis, which is positive. Secondly, for the factorial of the number \(2\) we will have that:

\[2!=2\times 1!=2\times (1\times 0!)=2\times (1\times 1)=2\times 1=2\]

which means that there is no ambiguity. Finally, we can see that it makes no sense trying to determine the factorial value of \(-1\) or \(4.5\) because this function can only be applied to \(\mathbb{N}_0\) members}.

This example shows that, even in mathematics, there are different levels of rigour in the different ways that is possible to transmit knowledge. Some require more imagination than others but in general they have been enough for Mankind to preserve knowledge throughout history.

It so happens that nowadays Mankind has a partner that has been giving a huge contribution to its progress: the computer. This machine has the extraordinary capability of instructed on how to execute a complex set of tasks. Programming is essentially all about transmitting to a computer the knowledge needed to solve a specific problem. This knowledge is called a program. Because they are programable, computers have been used for the most diversified ends, and in the last decades they have radically changed the way we work. Unfortunately, the computer’s extraordinary ability to learn comes with an equal extraordinary lack of imagination. A computer does assume or imagine, it just rigorously interprets the knowledge transmitted in the form of a program.

Since it has no imagination the computer depends critically on the way we present it the knowledge that we wish to transmit: that knowledge must be described in such a way that no ambiguity, gaps or imprecision. A language with these characteristics is generally called a programming language.

2.1 Programming Languages

For a computer to be able to solve a problem it is necessary to describe the process of solving a problem in a language that it understands. Unfortunately, the language that a computer "innately" understands is extremely poor, making the description of how to solve a non-trivial problem a very exhausting, tedious and complex one. The countless programming languages that have been invented aim at lighting the programmer’s burden, by introducing linguistic elements capable of simplifying those descriptions. For example the concept of function, sum, matrix or rational number do not exist natively in computers but many programming languages allow their usage in order to simplify the description of scientific calculus. Naturally, there must be a process that is able to transform the programmer’s descriptions into instructions that computers can understand. Although this process is relatively complex what matters is that it allows us to have programming languages that operate closer to human thinking process rather than the computer’s.

This last fact is of extreme importance because it allows us to use programming languages not only to instruct a computer on how to solve a problem, but also to explain that process accurately to another human being. This way, programming languages become a way of transmitting knowledge as mathematics has been for the last thousands of years.

There is a huge amount of programming languages, some better equipped than others to solve specific problems. The choice of a programming language should therefore depend heavily on the type of the problems we wish to solve but it should not be a total commitment. For a programmer it is much more important to understand the fundamentals and techniques of programming than to master this or that language. However, to better understand these fundamentals, it’s convenient to exemplify them in a concrete programming language.

As the focus of this document will be on programming for Architecture, we will use a programming language that is geared towards solving geometrical problems. There are many languages that serve this purpose, most commonly associated with computed aided design tools - Computer Aided Design (CAD). ArchiCAD, for instance, offers a programming language called GDL, an acronym for Geometric Description Language that enables users to program multiple geometric forms. In the case of AutoCAD that language used is called AutoLisp, a dialect of a famous programming language called Lisp. A third option will be the RhinoScript language, available for Rhinoceros. Despite these languages seeming very different from each other, the concepts behind them are very similar. It is on these concepts that we will be leaning on, although for pedagogical reasons, it is convenient to particularize them in a single language.

Unfortunately GDL, AutoLisp, and RhinoScript were developed a long time ago and they have not been updated, possessing many archaic characteristics that makes them harder to learn and use. In order to make the learning process easier and, simultaneously allowing our programs to run in different CAD environments, we are going to use a new language called Racket, that has was purposely adapted for programming in Architecture. In this text we will explain the fundamentals of programming using Racket, not just because its easier to learn, but also for it’s practical applicability. However, once learned, the reader should be able to apply these fundamentals to any other programming language.

In order to facilitate the programmer’s task, Racket is equipped with a programming environment called DrRacket, that offers a text editor adapted to edit Racket’s programs, as well as a set of additional tools for error detection and debugging. This programming environment is shared with a freeware license and it is available at:

2.1.1 Exercises 1
2.1.1.1 Question 1

Exponentiation \(b^n\)is an operation between two numbers \(b\) and \(n\). When \(n\) is a positive integer, exponentiation is defined as:

\[b^n = \underbrace{b \times b \times \cdots \times b}_n\]

To a reader not familiarized with exponentiation, the previous definition raises several questions that may not be evident: how many multiplications should actually be done?,\(n\)?, \(n-1\)? What if \(b=1\)? or \(b=0\)? Propose a definition for the exponentiation function in order to clear any doubts.

2.1.1.2 Question 2

What is a program and what purpose does it serve?

2.1.1.3 Question 3

What is a programming language and what purpose does it serve?

2.2 The Racket Language

In this section we will learn about Racket programming language, which we will use throughout this text. But first, we are going to examine some aspects that are common to other languages.

2.2.1 Syntax, Semantics and Pragmatics

Every language has syntax, semantics, and pragmatics.

In simple terms, syntax is a set of rules that dictate the kind of sentence that can be written in that language. Without it, any concatenation of words could be a sentence. For example, given the words “John”, “cake”, “ate”, “the” the syntax rules of the English language tell us that - “John ate the cake” is a correct sentence, and that - “ate the Jonh cake” is not. Note that according to the English syntax, "The cake ate John" is also syntactically correct.

Syntax dictates how a sentence is constructed but says nothing in regards to its meaning. Semantics are what attributes meaning to a sentence, thus telling us that “The cake ate John” makes no sense.

Finally, pragmatics sets the way sentences are commonly expressed. In a language, pragmatic changes depending on the context: the way two close friends talk with each other is different from the way two strangers talk.

These three aspects of a language are also present when we discuss programming languages. Unlike the natural languages we use to communicate between us, programming languages are characterized as being formal, obeying a set of simple and restrictive rules that can be mechanically processed.

In this document we will describe Racket’s syntax and semantics and, although there are mathematical formalisms to describe rigorously those two aspects, they require a mathematical sophistication that, given the nature of this work, is inappropriate. So we will only use informal descriptions. Afterwards, as we introduce language elements, we will discuss language pragmatics’.

2.2.2 Syntax and Semantics of Racket

When compared to other programming languages, Racket’s syntax is extraordinary simple and is based on the concept of expressions.

An expression in Racket can be formed using primitive elements such as numbers; or by the combination of those elements such as the sum of two numbers. This simple definition allows us to build expressions of arbitrary complexity. However, it is important to remember that syntax restricts what can be written: the fact that we can combine expressions to create more complex ones, that does not mean we can write any combination of sub-expressions. These combinations are restricted by syntactic rules that we will describe throughout this text.

Much like the syntax, Racket’s semantics is also very simple when compared to other programming languages. As we will see, semantics is determined by the operators that are used in our expressions. For instance, the sum operator, is used to add two numbers. An expression that combines this operator with, for example, the numbers \(3\) and \(4\) will have as its meaning the sum between \(3\) and \(4\), i.e., \(7\). In a programming language, the semantics’ of an expression is given by the computer that will evaluate the expression.

2.2.3 The Evaluator

Every expression in Racket has a value. This concept is so important that Racket provides an evaluator, i.e., a program designed to interact with the user in order to evaluate expressions defined by the user.

In Racket, the evaluator is shown as soon as we start working the DrRacket environment, and it is possible to easily change between the editor and the evaluator at any time. Once DrRacket is running, the user is presented with the character > (called prompt), meaning that Racket is waiting for the user to input an expression.

The character ">" is Racket’s "prompt", in front of which the user’s expressions will be shown. Racket interacts with the user by executing a cycle that reads an expressions, determines its value and writes the result. This cycle is traditionally called read-eval-print-loop (abbreviated to REPL).

During the read phase, Racket reads an expression and creates an internal object that represents it. In the evaluation phase, that object is analysed in order to produce a value. This analysis uses rules that dictate, for each case, the object’s value. Finally, the result is given back in text form to the user in the print phase.

Given the existence of the read-eval-print-loop process, in Racket it is not necessary to instruct that computer to explicitly print the result of an expression, meaning that testing and debugging is significantly easy. The advantage of Racket being an interactive language is that it allows programs to be quickly developed by writing, testing and correcting small fragments at a time.

2.3 Language Elements

In every programming language we have to deal with two sets of objects: data and procedures. Data comprise all the entities that we wish to manipulate. Procedures designate the rules on how to manipulate that data.

In Mathematics, we can look at numbers as the data and algebraic operations as the procedures. These operations allows us to combine numbers. For example, \(2\times 2\) is a combination. Another combination involving more data is \(2\times 2\times 2\), and using even more data \(2\times 2\times 2\times 2\). However, unless we want to spend time solving problems of elementary arithmetic, we should consider more elaborate operations that represent calculation patterns. In the previous sequence of combinations shown, it is clear that the pattern that is emerging is the definition of exponentiation, which has been defined in Mathematics a long time ago. Exponentiation is therefore an abstraction of a succession of multiplications.

As in Mathematics, a programming language should contain primitive data and procedures, it should be capable of combining data and procedures to create more complex data and procedures and it should be able to abstract calculation patterns and allow them to be used as simple operations, defining new operations that represent those patterns.

Further ahead we are going to see how it is possible to define these abstractions in Racket. But for now, let us take a closer look at the primitive elements of the language, i.e., the most simple entities that the language deals with.

2.3.1 Numbers

As said previously, Racket executes a read-eval-print cycle. This implies that everything we write in Racket must be evaluated, i.e., everything must have a value that Racket displays on the screen.

That way, if we give the evaluator a number it will return the value of that number. How much is the value of a number? At best we can say it has its own value. For example, the value of 1 is 1.

> 1

1

> 12345

12345

> 1/2

1/2

> 1+2i

1+2i

> 4.5

4.5

In Racket, numbers can be exact or inexact. Exact numbers include integers, fractions and complex numbers with integer parts. Inexact numbers are all others, being typically written in decimal or scientific notation).

2.4 Combinations

A combination is an expression that describes the application of an operator to its operands. In Mathematics, numbers can be combined using operations like the sum or multiplication; e.g. \(1 + 2\) and \(1 + 2 \times 3\). The sum and multiplication of numbers are but two of the extremely primitive procedures provided by Racket.

In Racket, a combination can be created by writing a sequence of expressions inside a pair of parentheses. An expression is a primitive element or another combination. The expression (+ 1 2) is a combination of two primitive elements 1 and 2 through the primitive procedure +. In the case of (+ 1 (* 2 3)) the combination is between \(1\) and (* 2 3) (this last expression is also a combination). Note that each expression must be separated from the rest using at least one space. Despite the combination (* 2 3) having three expressions - *, 2 and 3, the combination (*2 3) only has two - *2 and 3, in which the first expression has no predefined meaning.

For now, the only useful combinations are those in which expressions have meaning as operators and operands. Conventionally, Racket considers the first element of the combination the operator and the rest its operands.

The notation Racket uses to build expressions (the operator first and then the operands) is called prefix notation. This form of notation can cause some perplexity to new users of this language since most of them expect a notation closer to that taught in arithmetic and which is usually used in other programming languages. The expression (+ 1 (* 2 3)) is normally written 1 + 2 * 3 (designated infix notation, operator between operands), and usually this is easier for a human being to read. However, the prefix notation used by Racket has advantages over the infix notation:

Besides the infix and prefix notations, there is also the postfix notation in which the operator comes after the operands. This notation has the same properties as the prefix notation but it is, in general, more difficult to read since it is necessary to read every operand before we are able to understand what should be done with them.

2.4.1 Indentation

The disadvantage of the prefix notation is the writing of complex combinations. The expression 1+2*3-4/5*6 is easy to read but when written using Racket’s syntax, (- (+ 1 (* 2 3)) (* (/ 4 5) 6)), it has a form that for those not yet accustomed to this syntax can be harder to read due to the large number of parentheses.

To make the expression easier to read, we can (and we should) use indentation. This technique is based on using different alignments in the textual disposition of programs in order to make them easier to read. This way, instead of writing our expressions in a single line or with an arbitrary line arrangement, we write them throughout several lines and with an alignment between lines that shows how the sub-expressions are related to the expression that contains them.

The rule for indentation in Racket is extremely simple: in one line we have the operator and the first operand, the remaining operands are placed immediately below the first on, with enough blank spaces on the left so they are correctly aligned. If the case of a short expression, we can write it in a single line, with the operands immediately after the operator, using a single blank space to separate them. Using these two rules, we can rewrite the previous expression in the following manner:

(- (+ 1
      (* 2 3))
   (* (/ 4 5)
      6))

Note that arranging a combination in several lines does not affect the way Racket reads it. The correct delimitation of elements in a combination is done only by the visual separation and the correct usage of stacked parenthesis.

When the indentation rule is not enough to produce an aesthetically pleasing disposition of text lines, some minor variations may be used, such as placing the operator in a line and the operands underneath it, like in the following example:

(an-operator-with-a-very-very-very-big-name
  1 2 3 4)

Generally, we might need to apply several rules simultaneously:

(an-operator (an-operator-with-a-very-very-very-big-name
               1 2 3 4)
             (another-operator 5
                               6
                               (and-another 7
                                            8))
             (and-the-last-one 9 10))

Some operators have a proper indentation rule but this will be explained as we introduce them.

Indentation is crucial in Racket because it makes it easier to write and read complex code. Most editors that recognize Racket’s language automatically format the programs as we write them, also showing the correct matching between parenthesis. Eventually with practice, it becomes every easy to write and read programs, regardless how complex their structure is.

2.4.2 Exercises 2
2.4.2.1 Question 4

Define REPL?

2.4.2.2 Question 5

What is the difference between prefix an infix notation?

2.4.2.3 Question 6

In Mathematics it is usual to use simultaneously the prefix, infix and postfix notations. Write some mathematical examples of expressions that make use of those different notations.

2.4.2.4 Question 7

Convert the following arithmetic infix expressions to Racket’s prefix notation:
  1. \(1 + 2 - 3\)

  2. \(1 - 2 \times 3\)

  3. \(1 \times 2 - 3\)

  4. \(1 \times 2 \times 3\)

  5. \((1 - 2) \times 3\)

  6. \((1 - 2) + 3\)

  7. \(1 - (2 + 3)\)

  8. \(2 \times 2 + 3 \times 3 \times 3\)

2.4.2.5 Question 8

Convert the following Racket prefix notation expressions into arithmetic infix ones:
  1. (* (/ 1 2) 3)

  2. (* 1 (- 2 3))

  3. (/ (+ 1 2) 3)

  4. (/ (/ 1 2) 3)

  5. (/ 1 (/ 2 3))

  6. (- (- 1 2) 3)

  7. (- 1 2 3)

2.4.2.6 Question 9

Use indentation to rewrite the following expression, so that there is only a single operand per line.

(* (+ (/ 3 2) (- (* (/ 5 2) 3) 1) (- 3 2)) 2)

2.4.3 Evaluating Combinations

As we have seen, Racket considers the first element of a combination its operator and the rest the operands.

The evaluator determines the combination’s value by applying the procedure specified by the user to the value of the operands. The value of an operand is designated as the argument of the procedure. The value of the combination (+ 1 (* 2 3)) is the result of adding the value of 1 to (* 2 3). As we have already seen, the value of 1 is \(1\) and (* 2 3) is a combination whose value is the result of multiplying 2 by 3, which is \(6\). Finally, by summing \(1\) with \(6\) we get \(7\).

> (* 2 3)

6

> (+ 1 (* 2 3))

7

2.4.3.1 Question 10

Calculate the value of the following Racket expressions:

  1. (* (/ 1 2) 3)

  2. (* 1 (- 2 3))

  3. (/ (+ 1 2) 3)

  4. (- (- 1 2) 3)

  5. (- 1 2 3)

  6. (- 1)

2.4.4 Strings

Chains of characters (also called Strings) are another type of primitive data. A character is a letter, a digit or any kind of graphic symbols, including non-visible graphic symbols like blank spaces, tabs and others. A string is specified by a character sequence between quotations marks. Just like with numbers, the value of a string is the string itself:

> "Hi"

"Hi"

> "I am my own value"

"I am my own value"

Since a string is delimited by quotation marks, one could ask how can we create a string that contains quotation marks. For this and other special characters, there is a special character that Racket interprets differently: when in a strings the character \ appears it tells Racket that the next characters must be evaluated in a special way. For example, to create the following string:

John said "Good morning!" to Peter.

We must write:

"John said \"Good morning!\" to Peter."

Some valid escape characters in Racket.

Sequence

 

Result

\\

 

the character \ (backslash)

\"

 

the character " (quotation marks)

\e

 

the character escape

\n

 

new line character (newline)

\r

 

new line character (carriage return)

\t

 

tab character (tab)

The character \ is called an escape character and allows the inclusion of characters in strings that would otherwise be difficult to input. this table) shows examples of other escape characters.

As it happens with numbers, there are countless operators to manipulate strings. For example, to concatenate multiple strings there is the string-append operator. The concatenation of several strings produces a single string with all characters of those strings, in the same order:

> (string-append "1" "2")

"12"

> (string-append "one" "two" "three" "four")

"onetwothreefour"

> (string-append "I" " " "am" " " "a" " " "string")

"I am a string"

> (string-append "And I" " am " "another")

"And I am another"

To know how many characters there are in a string we have the string-length operator:

> (string-length "I am string")

11

> (string-length "")

0

Note that quotation marks define strings’ boundaries and are not consider characters. Besides strings and numbers, Racket has other kinds of primitive elements that will be addressed later.

2.5 Defining Functions

In addition to basic arithmetic operations, Mathematics offers a large set of operations that are defined based on those basic ones. For example, the square of a number is an operation (also designated as a function) that, given a number, calculates the multiplication of that number by itself. This function has the following mathematical definition \(x^2 = x \cdot x\).

Like in Mathematics, it is possible to define the square function in a programming language. In Racket, to obtain the square of a number, for example 5, we write the combination (* 5 5). In general, given a number x, we know to obtain the square value by writing (* x x) combination. All that remains now is to associate a name indicating that, given a number x, we obtain its square by evaluating (* x x). Racket allows us to do that by using the define operation:

(define (square x) (* x x))

As you can see from the square function definition, in order to define a function in Racket, we need to combine three elements. The first element is the word define, that informs the evaluator that we are defining a function. The second element is a combination with the function’s name and its parameters. The third element is the expression that will compute the function value for those parameters. In generic terms we could say that the definition of functions is done in the following manner:

(define (name parameter1 ... parametern)
  body)

The function’s parameters are called formal parameters and they are used in the body of an expression to refer to the correspondent arguments. When we write in the evaluator (square 5), the number 5 is the formal parameter. During the calculation this argument is associated with the number x. The arguments of a function are also called actual parameters.

The definition of the square function declares that in order to determine the square of a number x, we should multiply that number by itself (* x x). This definition associates the word square with a procedure, i.e., a description on how to obtain the desired result. Note that this procedure has parameters allowing it to use different arguments. For example, let’s evaluate the following expressions:

> (square 5)

25

> (square 6)

36

The rule to evaluate combinations stated above is also valid for functions defined by the user. The evaluation of the expression (square (+ 1 2)) first evaluates (+ 1 2) operand. This value of this operand, \(3\), is used by the function in place of x. The function’s body is then evaluated and all occurrences of x will be replaced by the value 3, i.e., the final value will b e the combination (* 3 3).

Formally, in order to invoke a function, is necessary to construct a combination in which the first element is an expression that evaluates for the function itself, and the remaining elements are expressions that evaluate for the arguments that the function is supposed to use. The result of the combination’s evaluation is the value calculated by the function for those arguments.

The process of evaluating a combination is done by the following steps:

  1. All elements in a combination are evaluated, with the value of the first one being necessarily a function.

  2. The formal parameters are associated with the function’s arguments, i.e., the value of the remaining elements of that combination. Each parameter is associated to an argument, according to the order of parameters and arguments. An error occurs when the number of parameters and arguments is different.

  3. The function’s body is evaluated keeping in mind these associations between parameters and arguments.

To better understand this process, it is useful to decompose it in its most elementary steps. The following example shows the process of evaluating the expression \(((1+2)^2)^2\) step by step:

(square (square (+ 1 2)))
\(\downarrow\)
(square (square 3))
\(\downarrow\)
(square (* 3 3))
\(\downarrow\)
(square 9)
\(\downarrow\)
(* 9 9)
\(\downarrow\)
81

Every function created by the user is evaluated by Racket in equal terms as other predefined function. This allows them to be used to create new functions. For example, after defining the square function, we can define the function that calculates the area of a circle with radius \(r\), using the formula \(\pi * r^2\):

(define (circle-area radius)
  (* pi (square radius)))

Naturally, during the evaluation of the expression used to compute the area of a circle, the square function will be invoked. This is visible in the following evaluation sequence:

(circle-area 2)
\(\downarrow\)
(* pi (square 2))
\(\downarrow\)
(* 3.14159 (square 2))
\(\downarrow\)
(* 3.14159 (* 2 2))
\(\downarrow\)
(* 3.14159 4)
\(\downarrow\)
12.5664

Since defining functions allows for associations to be established between a procedure and a name, that means Racket needs to have memory in which to store those associations. This memory is called environment.

Note that this environment exists only while we are working. When the program is shut down, that environment is lost. In order to avoid losing those definitions, they should be saved as in a file. This is the reason why the process of working in Racket is based on writing the definitions in files although still needing to test them using the evaluator to ensure the proper behaviour of the created definitions.

2.5.1 Exercises 3
2.5.1.1 Question 11

Define the function double, that calculates the double of a given number.

2.6 Names

The definition of functions in Racket involves assigning names: names for functions and names for its parameters.

Racket presents almost no limit towards names that you can give. A name like square is as valid as x+y+z because what separates a name from the other elements of a combination are parentheses and blank spaces.

The blank space concept includes tabs and changing line.

In Racket the only characters that cannot be used in names are the parentheses (, apostrophe , quotation marks " and semicolons ;. All other characters can be used in names, but, in practice, the creation of names should take some rules in consideration:

The choice of names will have a significant impact on the program’s legibility. Let us consider for example the area \(A\) of a triangle with a base \(b\) and a height \(c\) which can be defined mathematically by:

\[A(b,c) = \frac{b \cdot c}{2}\]

In Racket’s language we will have:

(define (A b c) (/ (* b c) 2))

Note that the Racket definition is identical to the corresponding mathematical expression, apart from the prefix notation and the \(=\) symbol being define. However, if we did not know beforehand what the function does, we will hardly understand it. Therefore, and contrary to Mathematics, the names that we assign in Racket should have a clear meaning. Instead of writing "A" it is preferable that we write "triangle-area" and instead of writing "b" and "c" we should write "base" and "height" respectively. Taking these aspects in consideration we can present a more meaningful definition:

(define (triangle-area base height)
  (/ (* base height) 2))

As the number of definitions grow, names become particularly important for the reader to quickly understand the written program, so it is crucial that names are carefully chosen.

2.6.1 Exercises 4
2.6.1.1 Question 12

Suggest an appropriate name for the following functions:
  • Function that calculates the volume of a sphere;

  • Function that tests if a number is a prime-number;

  • Function that converts a measurement in centimetres into inches.

2.6.1.2 Question 13

Define the function radians<-degrees that receives an angle in degrees and computes the corresponding value in radians. Note that \(180\) degrees are \(pi\) radians.

2.6.1.3 Question 14

Define the function degrees<-radians that receives an angle in radians and computes the corresponding value in degrees.

2.6.1.4 Question 15

Define a function that calculates the perimeter of a circle given the radius \(r\).

2.6.1.5 Question 16

Define a function that calculates the volume of a parallelepiped from its length, width and height.

2.6.1.6 Question 17

Define a function that calculates the volume of a cylinder a length and radius. The volume corresponds to multiplying the base radius with the cylinder’s length.

2.6.1.7 Question 18

Define a function average that calculates the average value between two numbers. For example: (average 2 3) \(\rightarrow\) 2.5.

2.7 Predefined Functions

The possibility of defining new functions is fundamental for increasing the language’s flexibility and its ability to adapt to the problems we want to solve. The new functions, however, must be defined in terms of others that were either defined by the user or, at most, were already pre-defined in the language.

As we will see, Racket has a reasonable set of predefined functions that in most cases they are enough for what we want to do, but we should not try to avoid defining new functions whenever we deem it necessary.

In this table we see a set of mathematical functions predefined in Racket. Note that, due to syntax limitations (that are also present in other languages), it is common that some Racket functions have a notation that is different when compared to the mathematical definition. For example, the square root function, \(\sqrt{x}\) is written as (sqrt x). The name sqrt is a contraction of the words square root and similar contractions are used for several other functions. The absolute value function is written as \(|x|\) and the exponentiation function \(x^y\) as (expt x y). This table shows some equivalents between the invocations of Rackets functions and the correspondent Mathematics invocations.

Some math predefined functions in Racket.

Function

 

Arguments

 

Result

+

 

Multiple Numbers

 

The sum of all arguments. With no arguments, zero.

-

 

Multiple Numbers

 

With only one argument, the symmetric value. For more than one argument, the subtraction of the first argument by all the remaining arguments. With no arguments, zero.

*

 

Multiple Numbers

 

The multiplication of all arguments. With no arguments, zero.

/

 

Multiple Numbers

 

The dividision of the first argument by all the remaining arguments. With no arguments, zero.

add1

 

One number

 

The sum of the argument with 1.

sub1

 

One number

 

The subtraction of the argument with 1.

abs

 

One number

 

The absolute value of the argument.

sin

 

One number

 

The sine of the argument (in radians).

cos

 

One number

 

The cosine of the argument (in radians).

atan

 

One or two numbers

 

With only one argument, the inverse tangent of the argument (in radians). With two arguments,the arc tangent of the division between the first and the second. The argument’s sign is used to determine the quadrant.

sqr

 

One number

 

The square of the argument.

sqrt

 

One number

 

The square root of the argument.

exp

 

One number

 

The exponential value with \(e\) base of the argument.

expt

 

Two numbers

 

The first argument raised to the second one.

log

 

One number

 

The logarithmic value of the argument.

max

 

Multiple Numbers

 

The highest argument.

min

 

Multiple Numbers

 

the lowest argument.

remainder

 

Two numbers

 

With two arguments, the remainder of the division between the first and the second argument.

floor

 

One number

 

The argument without the fractional part.

Racket’s predefined math functions.

Racket

 

Mathematics

(+ x0 x1 ... xn)

 

\(x_0 + x_1 + \ldots + x_n\)

(+ x)

 

\(x\)

(+)

 

\(0\)

(- x0 x1 ... xn)

 

\(x_0 - x_1 - \ldots - x_n\)

(- x)

 

\(-x\)

(-)

 

Error

(* x0 x1 ... xn)

 

\(x_0 \times x_1 \times \ldots \times x_n\)

(* x)

 

\(x\)

(*)

 

\(1\)

(/ x0 x1 ... xn)

 

\(x_0 / x_1 / \ldots / x_n\)

(/ x)

 

\(x\)

(/)

 

Error

(add1 x)

 

\(x+1\)

(sub1 x)

 

\(x-1\)

(abs x)

 

\(|x|\)

(sin x)

 

\(\sin x\)

(cos x)

 

\(\cos x\)

(atan x)

 

\(\arctan x\)

(atan y x)

 

\(\arctan \frac{y}{x}\)

(sqr x)

 

\(x^2\)

(sqrt x)

 

\(\sqrt{x}\)

(exp x)

 

\(e^x\)

(expt x y)

 

\(x^y\)

(log x)

 

\(\log x\)

(floor x)

 

\(\lfloor x\rfloor\)

2.7.1 Exercises 5
2.7.1.1 Question 19

Translate the following mathematical expressions into Racket’s notation:
  1. \(\sqrt{\frac{1}{\log 2^{\left|(3-9\log 25)\right|}}}\)

  2. \(\frac{\cos^4 \frac{2}{\sqrt 5}}{\arctan 3}\)

  3. \(\frac{1}{2} + \sqrt 3 + \sin^{\frac{5}{2}} 2\)

2.7.1.2 Question 20

Translate the following Racket expressions into mathematical notation:
  1. (log (sin (+ (expt 2 4) (/ (floor (atan pi)) (sqrt 5)))))

  2. (expt (cos (cos (cos 0.5))) 5)

  3. (sin (/ (cos (/ (sin (/ pi 3)) 3)) 3))

2.7.1.3 Question 21

Define the function odd? that, for a given number evaluates if it is odd, i.e., if the remainder of that number when divided by two is one. In order to do so, you can use the predefined function remainder

2.7.1.4 Question 22

The area \(A\) of a pentagon inscribed in a circle of radius \(r\) is given by the following expression: \[A = \frac{5}{8}r^2\sqrt{10 + 2\sqrt{5}}\] Define a function to calculate that same area.

2.7.1.5 Question 23

Define a function to calculate the volume of an ellipsoid with semi-axis \(a\), \(b\) and \(c\). That volume can be calculated using the formula: \(V=\frac{4}{3}\pi a b c\)

2.8 Arithmetic in Racket

We saw previously that Racket is capable of dealing with several types of numbers, from integers to complex numbers and also fractions. Some of those numbers like \(\sqrt 2\) or \(pi\), do not have a rigorous representation based in numerals and for that reason, Racket classifies them as inexact numbers, in order to emphasize that they are dealing with an approximated value. When an inexact number is used in an arithmetic operation, the result will also be inexact, so the inexactness is said to be contagious.

Finitude is another feature of the inexact numbers. Unlike exact numbers that theoretically have no limits, inexact numbers cannot surpass a certain limit, above which every number is represented by infinity, as you can see by the following example:

> (expt 10.0 10)

10000000000.0

> (expt 10.0 100)

1e+100

> (expt 10.0 1000)

+inf.0

Note that |+inf.0| (or |-inf.0|) is Racket’s way of saying that a number exceeds the inexact numbers representation capacity. The number is not infinite, as one might think, but merely a value excessively big for Racket’s capacity.

There is also another problem regarding inexact numbers: round-off errors. As an example, let us consider the obvious equality \((4/3-1)*3-1=0\) and let us compare the results that we get by using exact or inexact numbers in Racket:

> (- (* (- (/ 4 3) 1) 3) 1)

0

> (- (* (- (/ 4.0 3.0) 1.0) 3.0) 1.0)

-2.220446049250313e-16

As we can see, by using inexact numbers we cannot obtain a correct result and the problem is caused by the use of round-off errors: 3/4 can’t be represented with a finite number of digits. This round-off error is then propagated to the remaining operations which will produce a value that, even though not zero, is relatively close enough.

2.8.1 Exercises 6
2.8.1.1 Question 24

Translate the following definition into Racket: \[f(x)=x-0.1\cdot{}(10\cdot{}x-10)\]

2.8.1.2 Question 25

In Mathematical terms, whatever the argument used in the previous function, the result should always be \(1\) because \[f(x)=x-0.1\cdot{}(10\cdot{}x-10)=x-(x-1)=1\] Using the created function calculate the result of the following expressions and explain them:

(f 5.1)
(f 51367.7)
(f 176498634.7)
(f 1209983553611.9)
(f 19843566622234756.0)
(f 5.5377455871102e+20)
2.8.1.3 Question 26

We wish to create a flight of stairs with \(n\) treads, capable of covering a height \(a\) in meters. Considering that each step as a tread height \(h\) and a width \(d\) that obey to the following proportion: \[2h+d=0.64\]

define a function that, from a given height to cover and the number of treads, computes the length of the flight of stairs.

2.9 Name Evaluation

The primitive elements presented so far, such as numbers and strings, evaluate to themselves, i.e., the value of an expression composed only by a primitive element is the primitive element itself. With names this is no longer true.

Names have a special meaning in Racket. Note that when we define a function, it has a name. And its formal parameters have names as well. When a combination is written, the evaluator uses the function definition associated with the name that is the first element of the combination. This means that the value of the first element in a combination is the associated function. If we had defined the function square, as suggested previously in section Defining Functions, we could test this behaviour in the following expressions:

> (square 3)

9

> square

#<procedure:square>

As we can see, the value of the name square is an entity that Racket describes using a special notation. This entity is, as shown, a function. The same behaviour happens to any other predefined function:

> +

#<procedure:+>

> *

#<procedure:*>

As we have seen, the sum + and multiplication * signs are some of the predefined names of the language. For example, the symbol pi is also predefined and associated with an approximated value of \(pi\):

> pi

3.141592653589793

However, when the body of the expression is evaluated, the value of a name assigned to a parameter in a function is the corresponding argument during the function call. For example, in the combination (square 3), after the evaluator knows that the square value is a function by us defined and that 3 has the value \(3\), it evaluates the body of the function but associating the name x, whenever is necessary, to the same \(3\) that was previously associated with x.

2.10 Conditional Expressions

There are many operations in which the result depends on a test. For example, the mathematical function \(|x|\), that estimates the absolute value of \(x\) is equivalent to the inverse of a number if it is negative or the number itself otherwise. Using the mathematical notation we have that:

\[|x|= \begin{cases} -x, & \text{if $x<0$}\\ x, & \text{otherwise.} \end{cases}\]

This function needs to test if the argument is negative and choose one of two alternatives: it either evaluates for the number itself or for its symmetrical value.

These kind of expressions that depend on making one or more tests, are called conditional expressions.

2.10.1 Logical Expressions

A conditional expression follows the structure ”if expression then ..., otherwise ...”. The expression that determines whether to use the branch ”if” or the branch ”otherwise”, is called the logical expression and is characterized for having its value interpreted as either true or false. For example, the logical expression (< x 0) tests if the value of x is less than zero. If it is, the expression’s evaluation will return true, otherwise it will return false.

2.10.2 Logical Values

Some programming languages consider true and false as part of a special data type called logical or boolean data.

After George Boole, the English mathematician that invented the algebra of logic.

Other languages, like Racket, do not consider that these values should be treated as special data, just that the conditional expression considers some of the values as true and the remaining ones as false.

In Racket, conditional expressions consider only one of its values as false, represented as #f. Any other value that is different than #f is considered to be true. From the conditional expression point of view, the expression \(123\) is considered to be true. However, it makes little sense to a human user that a number is considered as true or false so a constant that represents true was introduced the language. This constant is represented by #t. If #f is the only value that represents falsehood and if #t is different than #f then #t necessarily represents truth.

2.11 Predicates

In the most usual case, a logical expression is a function applied to some arguments. In this case, the function used as the test case is known as a predicate. The test value is interpreted as true or false. So the predicate is a function that produces only true or false.

Despite the use of #t and #f it is important to know that not every predicate returns #t and #f exclusively. There are predicates produce different values from #t and #f.

2.11.1 Arithmetic Predicates

The mathematical relational operators \(<\),\(>\),\(=\),\(\leq\) and \(\geq\) are some of the most simple predicates. These operators compare numbers between each other. Their use in Racket follows the prefix notation and are written respectively <,>,=, <= and >=. Some examples are:

> (> 4 3)

#t

> (< 4 3)

#f

> (<= (+ 2 3) (- 6 1))

#t

2.12 Logical Operators

In order to combine logical expressions together we have the and, or and not operators. The and and the or operators accept any number of arguments. The not only accepts one. The value of such combinations is determined according to the following rules:

Note that although the meaning of false is clear, it necessarily corresponds to the value of #f, the meaning of true is not so clear because everything different than #f is considered to be true.

2.12.1 Exercises 7
2.12.1.1 Question 27

What is the value of the following expressions?
  1. (and (or (> 2 3) (not (= 2 3))) (< 2 3))

  2. (not (or (= 1 2) (= 2 3)))

  3. (or (< 1 2) (= 1 2) (> 1 2))

  4. (and 1 2 3)

  5. (or 1 2 3)

  6. (and #f 2 3)

  7. (or #f #f 3)

2.13 Predicates with a Variable Number of Arguments

An important property of the arithmetic predicates <,>,=, <= and >= is they accept any number of arguments. Whenever there is more than one argument, the predicate is applied sequentially to pairs of argument. That way,

(< e1 e2 e3 ... en-1 en)

is equivalent to
(and (< e1 e2)
(< e2 e3)
...
(< en-1 en))
. This property can be seen in the following examples:

> (< 1 2 3)

#t

> (< 1 2 2)

#f

2.14 Recognizers

Apart from relational operators, there are many other predicates in Racket, like zero?, that tests if the number is zero:

> (zero? 1)

#f

> (zero? 0)

#t

Note that zero? ends with an question mark because when a predicate is called, a question is being asked. For historical reasons, not all predicates in Racket follow this convention. However, when we define new predicates we should be mindful to use them.

Note that the operator zero? is used to recognize a particular element (zero) in a data type (numbers). These type of predicates are known as recognizers.

Another important set of predicates are the universal recognizers. These do not recognize one but all elements of a particular type of data. An universal recognizer accepts any kind of data as an argument and returns true if that elements belongs to the that same kind.

For example, to determine if a certain element is a number, we can use the number? predicate:

> (number? 1)

#t

> (number? "Two")

#f

And the string? predicate determines if entities are strings:

> (string? "Two")

#t

> (string? 3)

#f

Finally, there are predicates that recognize certain sub types of data, like the predicate integer? that recognizes integer values:

> (integer? 1)

#t

> (integer? 1.0)

#t

> (integer? 1.1)

#f

> (integer? 2/3)

#f

> (integer? "four")

#f

Note that similar to the number 1, the number 1.0 is also an integer. The first is an exact number and the second one is an inexact number which implies that operations involving 1.0 will produce inexact results. To distinguish between both types of numbers we have the predicates exact? and the inexact?:

> (exact? 1)

#t

> (exact? 1.0)

#f

> (inexact? 1)

#f

> (exact? 2/3)

#t

2.14.1 Exercises 8
2.14.1.1 Question 28

What is a conditional expression? What is a logical expression?

2.14.1.2 Question 29

What is a logical value? Which logic values does Racket incorporate?

2.14.1.3 Question 30

What is a predicate? Give examples of predicates used in Racket.

2.14.1.4 Question 31

What is a relational operator? Give examples of relational operators used in Racket.

2.14.1.5 Question 32

What is a logical operator? Give examples of logical operators used in Racket.

2.14.1.6 Question 33

What is a recognizer? What is an universal recognizer? Give examples in Racket.

2.14.1.7 Question 34

Translate the following mathematical expressions into Racket’s notation:
  1. \(x<y\)

  2. \(x\leq y\)

  3. \(x<y\wedge y<z\)

  4. \(x<y\wedge x<z\)

  5. \(x\leq y \leq z\)

  6. \(x\leq y < z\)

  7. \(x< y \leq z\)

2.15 Selection

If we look at the mathematical definition of absolute value:

\[|x|= \begin{cases} -x, & \text{if $x<0$}\\ x, & \text{otherwise} \end{cases}\]

we notice that it uses a conditional expression in the form of

\[\begin{cases} \text{consequent expression}, & \text{if logical expression}\\ \text{alternative expression}, & \text{otherwise} \end{cases}\]

that translates to common language as “if logical expression then consequent expression, otherwise alternative expression”.

The evaluation of a conditional expression is made through the evaluation of the logical expression and if it is true, the consequent expression is applied, if it is false then the alternative expression is applied.

The use of conditional expressions in Racket is even easier than in mathematics because it is based on a simple operator, the if operator, called a selection operator since it allows to choice between two alternatives. The syntax of the if operator is as follows:

(if logical-expression consequent-expression alternative-expression)

The value of a conditional expression that uses the operator if is computed in the following way:

  1. The logical-expression is evaluated;

  2. If the previous evaluation is true, then the combination value is the consequent-expression;

  3. Otherwise, if the logical expression turns out to be false, the combination value is the alternative-expression.

Such behaviour, identical to what we would have in Mathematics, can be verified by the following examples:

> (if (> 3 2)
       1
       2)

1

> (if (> 3 4)
       1
       2)

2

Using the if operator, we can now define the absolute value function by doing a simple translation from the mathematical definition to Racket:

(define (abs x)
  (if (< x 0)
    (- x)
    x))

The purpose of using the if operator is to define functions whose behaviour depends on one or more conditions. For example, if we consider the max function that receives two numbers as arguments and returns the highest. To define such function we only need to test if the first number is higher than the second one. If it is, the function returns the first argument, otherwise it returns the second one. Based on this logic we can write:

(define (max x y)
  (if (> x y)
    x
    y))

Another far more interesting example is the mathematical function sign \(\operatorname{sgn}\), also known as signum (latim for "sign"). This function could be interpreted as the dual function of the absolute value function because we will have that \(x=\operatorname{sgn}(x) |x|\). The sign function is defined as:

\[\operatorname{sgn} x = \begin{cases} -1 & \text{if $x<0$} \\ 0 & \text{if $x = 0$} \\ 1 & \text{otherwise}\end{cases}\]

In common language, we would say that if \(x\) is negative, the \(\operatorname{sgn} x\) value is \(-1\), otherwise, if \(x\) is \(0\), the value is \(0\), otherwise the value is \(1\). That shows that the above expression uses two conditional expressions stacked in the following way:

\[\operatorname{sgn} x = \begin{cases} -1 & \text{se $x<0$} \\ \begin{cases} 0 & \text{se $x = 0$} \\ 1 & \text{otherwise}\end{cases} & \text{otherwise}\end{cases}\]

To define this function in Racket, two ifs must be used:

(define (signum x)
  (if (< x 0)
    -1
    (if (= x 0)
      0
      1)))

2.16 Multiple Selection

When a conditional expression requires more than one if, it is possible that the code becomes increasingly harder to read. In this case there is an alternative called cond which makes the function’s definition easier to read. The syntax of cond is as follows:

(cond (expr0,0 expr0,1 ... expr0,n)
      (expr1,0 expr1,1 ... expr1,m)
      ...
      (exprk,0 exprk,1 ... exprk,p))

A cond receives any number of arguments. Each argument is called a clause and is made up of a list of expressions. The semantics of a cond is based on evaluating sequentially the first expression in each clause until one of them turns out to be true. In that case the consequent expressions are evaluated and the value of the last one is returned. If none of the clauses has a first expression that is true, the cond returns |#<void>| meaning there is no relevant result to be returned. If the clause, in which the first expression is true, has no other expressions then cond returns the value of that first expression.

It is important to note that the parenthesis around the clauses do not mean the clauses are combinations: they are simply part of the cond syntax and are necessary to separate clauses from each other.

The usual pragmatic used in a cond (especially when the clause only has two expressions) consists in aligning the expressions one under the other.

Using cond the sign function can be much easily defined:

(define (signum x)
  (cond ((< x 0)
         -1)
        ((= x 0)
         0)
        (#t
         1)))

Note that in the previous example the last cond clause has, as logical expression, the #t symbol. As we have seen, this last symbol represents true so its presence ensures that it will be evaluated in case none of the previous ones are. This way, a clause in the form of (#t ...) represents a "for everything else....". To make the reading task easier, Racket accepts a special form for this last situation: else. Using that syntax, the example can now be written as:

(define (signum x)
  (cond ((< x 0)
         -1)
        ((= x 0)
         0)
        (else
          1)))
2.16.1 Exercises 9
2.16.1.1 Question 35

Define the function sum-highest that given 3 numbers as arguments calculates the sum of the 2 with the highest value.

2.16.1.2 Question 36

Define the function max3 that given 3 numbers as arguments returns the highest value.

2.16.1.3 Question 37

Define the function second-highest that given 3 numbers as arguments and returns the second highest number: the number between the maximum and minimum value.

2.17 Local Variables

Let us consider the following triangle:

and try to define a function in Racket to calculate the triangle’s area from the parameters \(a\), \(b\) and \(c\).

One way of calculating the area of a triangle is to use the famous Heron’s formula:

Heron of Alexandria was an important Greek mathematician and engineer of the 1st century A.D. to whom numerous discoveries and inventions were credited to, including the steam engine and the syringe.

\[A=\sqrt{s\left(s-a\right)\left(s-b\right)\left(s-c\right)}\]

in which \(s\) is the triangle’s semi-perimeter:

\[s=\frac{a+b+c}{2}\]

When trying to use the Heron’s formula to write the equivalent in Racket we come across a small problem: the formula is (also) written in terms of the semi-perimeter \(s\), but \(s\) is not a parameter but rather a value that is derived from other parameters of the triangle.

One way of solving this problem is to replace \(s\) with its meaning:

\[A=\sqrt{\frac{a+b+c}{2}\left(\frac{a+b+c}{2}-a\right)\left(\frac{a+b+c}{2}-b\right)\left(\frac{a+b+c}{2}-c\right)}\]

From this formula it is now possible to define the function in Racket:

(define (area-triangulo a b c)
  (sqrt (* (/ (+ a b c) 2)
           (- (/ (+ a b c) 2) a)
           (- (/ (+ a b c) 2) b)
           (- (/ (+ a b c) 2) c))))

Unfortunately, this definition has two problems. The first one is the loss of correspondence between the original formula and the function definition, making it harder to recognize as the Heron’s formula. The second one is that the function is repeatedly using (/ (+ a b c) 2) which is a waste of human effort, because we had to write it four times, and a waste of computational effort, because the expression needs to be calculated four times, even though we know it always has the same value.

In order to solve this problem, Racket allows the use of local variables. A local variable only has meaning in the context of a function and is used to calculate intermediate values such as the semi-perimeter s. Using a local variable we can rewrite the triangle-area function:

(define (triangle-area a b c)
  (define s (/ (+ a b c) 2))
  (sqrt (* s (- s a) (- s b) (- s c))))

The semantics used when definition local variables is the same as the definition of regular ones, with the added subtlety that its context, i.e. the part of the program in which the defined name can be used, is confined to the function that it contains.

When calling the function triangle-area, giving it the arguments for the corresponding parameters a, b and c, it starts by introducing an additional name - s - associated to the value that comes from the expression (/ (+ a b c) 2) and, in the context of than new name, evaluates the remaining expressions in the function’s body. In practice it is as if the function was stating: "Knowing that \(s=\frac{a+b+c}{2}\), let us calculate \(\sqrt{s\left(s-a\right)\left(s-b\right)\left(s-c\right)}\)."

There is another way of defining local variables that, although semantically similar to the previous one, has the advantage of being usable in any part where an expression is expected. We can do that by using the let form. The redefinition of the previous function using the let form is as follows:

(define (triangle-area a b c)
  (let ((s (/ (+ a b c) 2)))
    (sqrt (* s (- s a) (- s b) (- s c)))))

Let uses the following syntax:

(let ((name0 expr0)
      (name1 expr1)
      ...
      (namen exprn))
  exprn+1
  exprn+2
  ...
  exprn+m)

The semantic of the let form consists of associating each name (#,(lispemphi name "i") to the corresponding expression expri and, in the context established by that association, evaluate the let’s body, i.e., evaluate the expressions from exprn+1 to exprn+m and return the value of the last one.

One important characteristic of the let form is the fact that the association between expressions and names does not depend on other associated names. This implies that the context for evaluating the body of let is established just once, and not incrementally. This can be explained in the following example:

> (let ((pi 3)
        (prev-pi pi))
    (+ pi prev-pi))

6.141592653589793

However, if we want the context to be established incrementally, thus allowing expressions to associate names that can depend on previous established associations, Racket provides the form let*: form:

> (let* ((pi 3)
         (prev-pi pi))
    (+ pi prev-pi))

6

The Let* form is formally equivalent to a cascade of let forms, as can be seen in the following example:

> (let ((pi 3))
    (let ((prev-pi pi))
      (+ pi prev-pi)))

6

2.18 Global Variables

Contrary to local names that have a limited context, a global name, is a name that can be seen in any context of our programs. Its context is therefore the entire program. The name pi represents the constant \(\pi=3.14159...\) and can be used in any part of our program. For that reason, pi is a global name.

The definition of global names is the same to that of local names, with the difference of being defined outside a function. Therefore, if we wish to introduce a new global name, for example, for the golden ratio:

Also known as gold proportion and divine proportion among other names, and abbreviated to \(\phi\) in honour of Fineas, a Greek sculptor responsible for building the Parthenon where, supposedly, this golden proportion was used. The golden ration was first introduced by Euclid when solving the problem of dividing a line segment into two parts such that the ratio between the line segment and the longest part was equal to the ratio between the longest part and the shortest part. If \(a\) is the length of the longest part and \(b\) the shortest part, the Euclid’s problem is equivalent to \(\frac{a+b}{a}=\frac{a}{b}\). As a result, \(a^2-ab-b^2=0\) or \(a=\frac{b\pm\sqrt{b^2+4b^2}}{2}=b\frac{1\pm\sqrt{5}}{2}\). What makes sense then is: \(a=b\frac{1+\sqrt{5}}{2}\). The expression for calculating the golden ratio is thus: \(\phi=\frac{a}{b}=\frac{1+\sqrt{5}}{2}.\)

\[\phi=\frac{1+\sqrt{5}}{2}\approx 1.6180339887\]

We simply need to write:

(define golden-ratio (/ (+ 1 (sqrt 5)) 2))

From this moment on, the golden-ration can be referenced in any part of our program.

It should be warned that global names should be limited, when possible, to defining just constants, like 2pi. Other useful examples may be pi/2, 4pi and pi/4, as well their symmetric values, that are defined in terms of:

(define 2pi (* 2 pi))
(define pi/2 (/ pi 2))
(define 4pi (* 4 pi))
(define pi/4 (/ pi 4))
(define -pi (- pi))
(define -2pi (- 2pi))
(define -pi/2 (- pi/2))
(define -4pi (- 4pi))
(define -pi/4 (- pi/4))

2.19 Modules

Every functionality of Racket is stored and organized modules. Every module is a unit containing a set of definitions. The Racket language is nothing more than an aggregation of modules that provide often required functionalities. There are many other functionalities in modules that we can only have access to if we specifically ask for it.

Take for example the following program:

#lang racket

(define (square x)

  (* x x))

 

(define (circle-area r)

  (* pi (square r)))

The first line indicates the language in which the program is written, in this case, Racket. The following lines are the definitions of our program that, naturally, make use of Racket’s functionalities. For this program that includes the define function, the arithmetic operation * and the pi value.

If it was necessary to have additional functionalities we would have to require it, using the form require. For example, if we want to visualize the \(\sin\) function graph, we could use the plot module, like so:

#lang racket

(require plot)

 

(plot (function sin (- pi) pi))

The plot module used above provides the means to visualize graphs, namely the functions plot and function, with the remaining names being provided by Racket’s module.

The require form can also be used in other ways, the most useful being to access Racket’s central modules repository, called planet (http://planet.racket-lang.org/). For example, for accessing the rosetta module, whose author is aml we should write:

#lang racket

(require (planet aml/rosetta))

2.19.1 Exercises 10
2.19.1.1 Question 38

Define a module called hyperbolic with the three following functions: hyperbolic sin (sinh), hyperbolic cosin (cosh) and hyperbolic tangent (tanh), based on the mathematical definitions: \[\sinh x = \frac{e^x-e^{-x}}{2}\] \[\cosh x = \frac{e^x+e^{-x}}{2}\] \[\tanh x = \frac{e^x-e^{-x}}{e^x+e^{-x}}\]

2.19.1.2 Question 39

In the same module, define the inverse hyperbolic functions: asinh, acosh, and atanh, whose mathematical definitions are:

\[\sinh^{-1} x=\ln(x+\sqrt{x^2+1})\] \[\cosh^{-1} x=\pm\ln(x+\sqrt{x^2-1})\] \[\tanh^{-1} x=\begin{cases} \frac{1}{2}\ln(\frac{1+x}{1-x}), & \text{se $|x|<1$}\\ \frac{1}{2}\ln(\frac{x+1}{x-1}), & \text{se $|x|>1$} \end{cases}\]

2.19.1.3 Question 40

Set the defined names, from the previous exercises, as available for other modules. Hint: See Racket’s documentation on provide.

3 Modelling

 (require rosetta/tikz) package: rosetta

We saw in previous sections some types of pre-defined data in Racket. In many cases these are enough to build our programs. But in other cases it will become necessary to introduce new types of data. In this section we will study a new type of data that will become particularly useful to model geometric entities: coordinates.

3.1 Coordinates

Architecture relies on positioning elements in space. That position is expressed in terms of of what we designate as Coordinates: each coordinate is a number and a sequence of coordinate identifies a point in space. this figure demonstrates one possible sequence of coordinates \((x,y,z)\) that identify a point \(p\) in a three-dimensional space. Different types of coordinate systems are possible and, in the case of this figure, we are using a system called \(rectangular\), also known as \(Cartesian\) in honour of its inventor: René Descartes.

Descartes was a 19th Century French philosopher, author of the famous quote: "Cogito ergo sum", (I think, therefore I am) and of countless contributions in the fields of Mathematics and Physics.

Cartesian coordinates of a point in space.

There are other types of useful operations that we can make using coordinates. For example, we can calculate the distance between two positions in space \(P_0=(x_0,y_0,z_0)\) and \(P_1=(x_1,y_1,z_1)\). That distance can be expressed using the formula:

\[d = \sqrt{(x_1-x_0)^2+(y_1-y_0)^2+(z_1-z_0)^2}\]

and a first draft of this definition in Racket would be:

(define (dist x0 y0 z0 x1 y1 z1)
  (sqrt (+ (sqr (- x1 x0))
           (sqr (- y1 y0))
           (sqr (- z1 z0)))))

The distance between \((2,1,3)\) and \((5,6,4)\) would then be:

> (dist 2 1 3 5 6 4)

5.916079783099616

Unfortunately, by treating coordinates as a set of three independent numbers the use of functions becomes unclear. This can already be seen in the previous example, where the function dist calls upon six parameters forcing the reader to know where the coordinates of one point start and where they end. This problem becomes even worse when a function must return, not a number as it so happens with the function dist, but rather a position in space, as it happens, for example, with the function that computes the mid position \(P_m\) between \(P_0=(x_0,y_0,z_0)\) and \(P_1=(x_1,y_1,z_1)\). That position can be calculated using the formula:

\[P_m=(\frac{x_0+x_1}{2},\frac{y_0+y_1}{2},\frac{z_0+z_1}{2})\]

But it is difficult to conceive a function that implements this because, apparently, it would have to calculate three different result simultaneously, one for each of the coordinates \(X\), \(Y\) and \(Z\).

To deal with this kind of problems, mathematicians came up with the concept of \(tuple\): a tuple is nothing more than group of values but, in certain cases, this grouping has a specific name. For example, a rational number is tuple since it groups a number and a denominator, the same way that a position in space is also a tuple since it groups three coordinates.

All programming languages have mechanisms to create and manipulate tuples. In Racket, in addition to the predefined tuples, like rational and complex numbers, various other tuples have already been defined in the module rosetta which will be very useful to us when modelling geometry.

To use them, we must first require the module rosetta

#lang racket

(require (planet aml/rosetta))

To set the coordinates \((x,y,z)\), Rosetta provides the function xyz:

> (xyz 1 2 3)

#<xyz:1 2 3>

> (xyz (* 2 3) (+ 4 1) (- 6 2))

#<xyz:6 5 4>

Note that the result from evaluating the expression (xyz 1 2 3) is a value that represents a position in the three-dimensional Cartesian space. That value is expressed as #<xyz:x y z> in which x, y, and z are the coordinate values.

3.2 Operations with Coordinates

Now that we know how to create coordinates, we can rethink the functions that manipulate them. Let us begin with the function that calculates the distance between two points \(P_0=(x_0,y_0,z_0)\) and \(P_1=(x_1,y_1,z_1)\), which, as we saw, can be calculated using the formula:

\[\sqrt{(x_1-x_0)^2+(y_1-y_0)^2+(z_1-z_0)^2}\]

A first draft of this definition translated into Racket would be:

(define (dist p0 p1)
  (sqrt (+ (sqr (- ? ?))
           (sqr (- ? ?))
           (sqr (- ? ?)))))

In order to complete the function we need to know how to get the \(x\), \(y\), and \(z\) coordinates of a certain position \(P\). For that purpose, Rosetta provides the functions cx, cy and cz, abreviations for ccoordinate x, ccoordinate y and ccoordinate z respectively.

Using these functions we can now write:

(define (dist p0 p1)
  (sqrt (+ (sqr (- (cx p1) (cx p0)))
           (sqr (- (cy p1) (cy p0)))
           (sqr (- (cz p1) (cz p0))))))

We can test is using a specific case:

The dist is pre-defined in Rosetta under the name distance.

> (dist (xyz 2 1 3) (xyz 5 6 4))

5.916079783099616

Let us look at another example. Suppose we wanted to define a function that calculates the position of a point after a translation, expressed in terms of its orthogonal components \(\Delta_x\), \(\Delta_y\) and \(\Delta_z\), as can be seen in this figure. For \(P=(x,y,z)\) we will have \(P'=(x+\Delta_x,y+\Delta_y,z+\Delta_z)\). To make the use of this function easier we shall call it +xyz. Naturally, it needs as inputs a starting point \(P\) and the increments \(\Delta_x\), \(\Delta_y\) and \(\Delta_z\) that we will call dx, dy, and dz, respectively.

The point \(P'\) as a result of the translation of point \(P=(x,y,z)\) of \(\Delta_x\) in the \(X\) axis, of the \(\Delta_y\) in \(Y\) axis and \(\Delta_z\) in the \(Z\) axis.

The definition of this function is as follows:

(define (+xyz p dx dy dz)
  (xyz (+ (cx p) dx)
       (+ (cy p) dy)
       (+ (cz p) dz)))

Naturally we ca now use +xyz to define new functions, as for example, the horizontal and vertical translation:

(define (+x p dx)
  (+xyz p dx 0 0))
 
(define (+y p dy)
  (+xyz p 0 dy 0))
 
(define (+z p dz)
  (+xyz p 0 0 dz))

Equally useful would be the functions that compute the diagonal translations through the orthogonal planes XY, XZ and YZ:

(define (+xy p dx dy)
  (+xyz p dx dy 0))
 
(define (+xz p dx dz)
  (+xyz p dx 0 dz))
 
(define (+yz p dy dz)
  (+xyz p 0 dy dz))

The effect of this function can be seen in this figure.

Translations made by +x, +y, +z, +xy, +xz, +yz and +xyz from an arbitrary \(P\) point and the by \(\Delta_x\), \(\Delta_y\) and \(\Delta_z\).

Note that every function defined in this section is based on the xyz, cx, cy and cz operations, which we can consider to be the fundamental operations on coordinates. The first one allows us to construct a position given three numbers and the others to know which numbers determine a position. For this reason, the first operation is said to be a constructor of coordinates and the others selectors of coordinates.

Although we are unaware on how these functions operate internally, we know they are consistent with each other, ensured by the following expressions:

(cx (xyz x y z)) \(=\) x

(cy (xyz x y z)) \(=\) y

(cz (xyz x y z)) \(=\) z

3.2.1 Exercises 11
3.2.1.1 Question 41

Define the function midpoint that calculates the three-dimensional coordinates of the midpoint between two points \(P_0\) and \(P_1\), given their three-dimensional coordinates.

3.2.1.2 Question 42

Define the function =c that compares two points coordinates and returns true if they are the coincident. Note that two points are coincident when their \(x\), \(y\), and \(z\) coordinates are equal.

3.2.2 Bi-dimensional Coordinates

Just as three-dimensional coordinates locate points in space, bi-dimensional coordinates locate points in a plane. The question asked is: which plane? From a mathematical point of view, this question is not relevant since it is perfectly possible to think about geometry in a plane without needing to visualize the plane itself. But when we try to visualize that geometry in a 3D modelling program we must inevitably think where that plane is located. If omitted, CAD applications will by default consider the bi-dimensional plane to be the \(XY\) plane with the height \(Z\) being zero. So let us consider the bi-dimensional coordinate \((x,y)\) as a simplified notation for the three-dimensional coordinate \((x,y,0)\).

Based on this simplification, we can define a bi-dimensional coordinates constructor in terms of the three-dimensional coordinates constructor:

(define (xy x y)
  (xyz x y 0))

One of the advantages of defining bi-dimensional coordinates as a particular case of three-dimensional coordinates is that the selectors cx, cy and cz are automatically applicable to bi-dimensional as well, as are the other functions we created, such as distance and the translations - +x, +y, +z, +xy, +xz, +yz and +xyz.

3.2.3 Exercises 12
3.2.3.1 Question 43

Given the point \(P_0=(x_0,y_0)\) and a line defined by two points \(P_1=(x_1,y_1)\) and \(P_2=(x_2,y_2)\), the minimum distance \(d\) between \(P_0\) and the line is given by:

\[d=\frac{\left\vert(x_2-x_1)(y_1-y_0)-(x_1-x_0)(y_2-y_1)\right\vert}{\sqrt{(x_2-x_1)^2+(y_2-y_1)^2}}\]

Define a function point-line-distance that given the coordinates of \(P_0\), \(P_1\) and \(P_2\) returns the minimum distance between \(P_0\) and the line defined by \(P_1\) and \(P_2\).

3.2.3.2 Question 44

Knowing that the maximum step height allowed for each step is \(0.18\)m define a function that calculates the minimum number of steps needed for a flight of stairs, shown in the following diagram, to connect \(P_1\) to \(P_2\).

3.2.4 Polar Coordinates

Although the Cartesian coordinate system is largely used, there are other coordinate systems that can be more useful in certain situations. As an example, suppose we wanted to set \(n\) elements, equally spaced between them and with distance \(d\) from the origin point, as is partially represented in this figure. Logically, the elements will eventually form a circle and it is easy to see that the angle between them would have to be \(\frac{2\pi}{n}\).

Positions along a circle.

Taking the \(X\) axis as reference, we can say that the first element will be positioned at \(d\) distance from the origin point, the second element will have the same distance but on a different axis that makes a \(\frac{2\pi}{n}\) angle with the \(X\) axis. The third element will have the same distance but in a different axis, making an angle of \(\frac{2\pi}{n}\) wit the \(X\) axis, and so on. However, when trying to define those positions using Cartesian coordinates we would find that the regularity expressed with the "and so on" is immediately lost. This should make us consider a different system of coordinates, namely the polar coordinate system.

As represented in this figure, a position in a bi-dimensional plane is expressed, in rectangular coordinates, by the numbers \(x\), and \(y\) - respectively the x-axis and y-axis, while in polar coordinates it is expressed by \(\rho\) and \(\phi\) - respectively the radius vector (also called module) and the polar angle (also called argument). With the help of trigonometry and the Pythagorean theorem it is easy to convert polar coordinates into rectangular coordinates:

\[\left\{\begin{aligned} x&=\rho \cos \phi\\ y&=\rho \sin \phi \end{aligned}\right.\]

or from rectangular coordinates to polar coordinates:

\[\left\{\begin{aligned} \rho&=\sqrt{x^2 + y^2}\\ \phi&=\arctan \frac{y}{x} \end{aligned}\right.\]

Rectangular and polar coordinates.

Based on the above equations we can define the constructor of polar coordinates pol (abbreviation of "polar") that will construct coordinates from its polar representation, by simply converting them into the equivalent rectangular ones:

(define (pol ro fi)
  (xy (* ro (cos fi))
      (* ro (sin fi))))

That being said, polar coordinate will be implemented based on the rectangular system. For that reason, the polar coordinates selectors - the function (pol-rho that allows us to obtain the \(\rho\) value and the function pol-phi that allows us to obtain the \(\phi\) value) - must use the rectangular coordinates selectors, i.e., cx and cy.

These functions are already predefined in Rosetta.

(define (pol-rho c)
  (sqrt (+ (sqr (cx c)) (sqr (cy c)))))
 
(define (pol-phi  c)
  (atan (cy c) (cx c)))

Here are some examples of these functions:

Note that in some cases the coordinates numbers are not zero or one as we would expect, but values very close to them. This is due to rounding errors. And also note that since we are using bi-dimensional coordinates the \(z\) coordinate is always zero.

> (pol 1 0)

#<xyz:1 0 0>

> (pol (sqrt 2) (/ pi 4))

#<xyz:1.0000000000000002 1.0 0>

> (pol 1 (/ pi 2))

#<xyz:6.123233995736766e-17 1.0 0>

> (pol 1 pi)

#<xyz:-1.0 1.2246467991473532e-16 0>

Another very useful operation is the one that, given a point \(P=(x,y)\) and a ""vector"" with its origin in \(P\) and specified in polar coordinates by a distance \(\rho\) and an angle \(\phi\), returns the the point located at the end of the vector, as shown in this figure. Using trigonometry it is easy to calculate that this position given by \(P'=(x+\rho\cos\phi, y+\rho\sin\phi)\).

A point displacement in polar coordinates

This translated into Racket becomes:

(define (+pol p ro fi)
  (+xy p
       (* ro (cos fi))
       (* ro (sin fi))))

Some examples of its use:

> (+pol (xy 1 2) (sqrt 2) (/ pi 4))

#<xyz:2.0 3.0 0>

> (+pol (xy 1 2) 1 0)

#<xyz:2 2 0>

> (+pol (xy 1 2) 1 (/ pi 2))

#<xyz:1.0 3.0 0>

3.2.5 Exercises 13
3.2.5.1 Question 45

The function =c defined in Question 42 compares the coordinates of two points, returning true if they are coincidental. However, taking into account that numeric operations can produce rounding errors it is possible that two coordinates, which in theory should be the same, in practice are not considered as such. For example, the point \((-1,0)\) in rectangular coordinates can be expressed in polar coordinates as \(\rho=1, \phi=\pi\) but Racket will not consider them equal, which can be seen in the example below:

> (=c (xy -1 0) (pol 1 pi))

#f

> (xy -1 0)

#<xyz:-1 0 0>

> (pol 1 pi)

#<xyz:-1.0 1.2246467991473532e-16 0>

As you can see, although the coordinates are not the same they are very close, i.e., the distance between them is very close to zero. Propose a new definition for the function =c based on the concept of distance between coordinates.

3.3 Bi-dimensional Geometric Modelling

In this section we will be introducing some bi-dimensional geometric modelling operations.

In order to visualize the shapes we create we need to have a CAD application, like AutoCAD or Rhino. The choice of which CAD application we wish to use is made using the backend function together with the argument (autocad or rhino). Therefore, a program that uses Rosetta usually starts with:

#lang racket

(require (planet aml/rosetta))

(backend autocad)

or with:

#lang racket

(require (planet aml/rosetta))

(backend rhino)

depending on the user’s preference for AutoCAD or Rhino, respectively.

Let us start by considering the creation of three circles. For that we can use the function circle which receives the centre point and the radius as arguments. In this figure you can see the result of the following program in AutoCAD:

From here onwards, we will omit the header that requires Rosetta and chooses the CAD application and, instead, we shall focus our attention on the operations for geometric modelling.

#lang racket

(require (planet aml/rosetta))

(backend autocad)

 

(circle (pol 0 0) 4)

(circle (pol 4 (/ pi 4)) 2)

(circle (pol 6 (/ pi 4)) 1)

A series of circles.

Another much used operation is the one that creates line segments: line. In its most simple form it takes the two positions of its extremities. However, it is possible to invoke this function with any number of positions, case in which it will successively connect each position with the next, forming a polygonal line. this figure shows the example of a swastika

The swastika is a mythical symbol, used by many cultures since the neolithic period

produced by the following code:

(line (xy -1 -1) (xy -1 0) (xy 1 0) (xy 1 1))
(line (xy -1 1) (xy 0 1) (xy 0 -1) (xy 1 -1))

A set of line segments.

In case we wish to draw closed polygonal lines it is preferable that we use the polygon function, very similar to the function line but with the difference that it creates an additional segment connecting the last position with the first. This figure shows the result of the following code:

(polygon (pol 1 (* 2 pi 0))
         (pol 1 (* 2 pi 1/5))
         (pol 1 (* 2 pi 2/5))
         (pol 1 (* 2 pi 3/5))
         (pol 1 (* 2 pi 4/5)))

A polygon.

And for drawing regular polygons, i.e., polygons that have equal edges and angles, as shown in this figure, it is preferable to use the function regular-polygon. This function receives as arguments the number of sides, its centre point, a radius, a rotation angle and a boolean to indicate if the radius refers to an inscribed circle (i.e. the radius is the distance from the edges to the centre) or a circumscribed circle (i.e. the radius is the distance from the vertexes to the centre). If omitted, the centre point will be considered the origin, the radius will have one unit of measurement, the angle will be considered zero and the circle will be circumscribed.

Using regular-polygon, this figure can be obtained by:

(regular-polygon 5)

More interesting examples can be obtained by changing the rotation angle. For example, the following expressions will produce the image shown in this figure:

(regular-polygon 3 (xy 0 0) 1 0 #t)
(regular-polygon 3 (xy 0 0) 1 (/ pi 3) #t)
(regular-polygon 4 (xy 3 0) 1 0 #t)
(regular-polygon 4 (xy 3 0) 1 (/ pi 4) #t)
(regular-polygon 5 (xy 6 0) 1 0 #t)
(regular-polygon 5 (xy 6 0) 1 (/ pi 5) #t)

Overlapping triangles, squares and pentagons with different rotation angles.

For four sided polygons aligned with the \(X\) and \(Y\) axis there is a very simple function: rectangle. This function can either be used with the position of its bottom left corner and upper right corner or the with the position of its bottom left corner and the two rectangle dimensions, as exemplified below and represented in this figure:

(rectangle (xy 0 1) (xy 3 2))
(rectangle (xy 3 2) 1 2)

A set of rectangles

In the following sections we will introduce the remaining functions available in Rosetta.

3.3.1 Exercises 14
3.3.1.1 Question 46

Recreate the drawing presented in this figure but this time using rectangular coordinates.

3.3.1.2 Question 47

We wish to place two circles with unit radius and around an origin so that the circles are tangent to each other as shown in the following drawing:

Write a sequence of expressions that when evaluated produce the above image.

3.3.1.3 Question 48

We wish to place four circles with unit radius and around an origin so that the circles are tangent to each other as shown in the following drawing:

Write a sequence of expressions that when evaluated produce the above image.

3.3.1.4 Question 49

We wish to place three circles with unit radius and around an origin so that the circles are tangent to each other as shown in the following drawing:

Write a sequence of expressions that when evaluated produce the above image.

3.4 Side Effects

In Racket, as we have previously seen, every expression has a value. We can see that by evaluating an arithmetic expression but also when evaluating a geometric expression:

> (+ 1 2)

3

> (circle (xy 1 2) 3)

#<circle 0>

In the last example, the result of evaluating the second expression is a geometric entity. When Racket writes a result that is a geometric entity it uses a notation based on the name of that entity and an integer to distinguish between them. Most of the times we are not only interested in seeing a geometric entity as a piece of text but we are also interested in visualizing that entities in space. For that purpose the evaluation of geometric expressions also has a side effect: all created geometrical forms are automatically added to the chosen CAD application using the backend function.

That way, evaluating the following program:

#lang racket

(require (planet aml/rosetta))

(backend autocad)

 

(circle (xy 1 2) 3)

produces as a result an abstract value that represents a circle with a radius of \(3\) units, centred at the point \(1,2\) and, as a side effect, that circle becomes visible in AutoCAD.

This behaviour of geometric functions like circle, line, rectangle, etc., is fundamentally different from the ones we have seen so far because previously these functions were used to compute something, i.e., to produce a value, and now it is not that value that interests us the most but rather its side effect (also called collateral effect) that allows us to visualize the geometrical shape in the CAD application.

One important aspect of using side effects is the possibility of their composition. The composition of side effects is accomplished through sequencing, i.e., the sequential computation of the different effects. In the next section we will discuss sequencing of side effects.

3.5 Sequencing

So far, we have combined mathematical expressions using mathematical operators. For example, from the expressions (sin x) and (cos x) we can calculate their division by (/ (sin x) (cos x)). This is possible because the evaluation of the sub-expressions (sin x) and (cos x) will produce two values that can then be used in the division.

With geometrical functions these kinds of combinations must be done differently since, as we saw, their evaluation also produces side effects. Seeing as these side effects are precisely what we want, Racket provides a way of producing them sequentially, i.e., one after the other. That feature is called begin and is used for sequencing side effects. For example, consider a function that draws a circle with a radius \(r\), centred on \(P\) with an inscribed or circumscribed square, depending on the user’s specification, as we show in this figure.

A circle with a square inscribed (left) and a square circumscribed (right).

The function’s definition could start as something like:

(define (circle-square p r inscribed?)
  ...)

The drawing produced by the function will naturally depend on the logic value of inscribed?, that is, this function’s definition could be something like:

(define (circle-square p r inscribed?)
  (if inscribed?
    "create a circle and a square inscribed in that circle"
    "create a circle and a square circumscribed in that circle"))

The problem now is that, for each case of if, the function must generate two side effects, namely create a circle and create a square. But the if only admits one expression, making us wonder how we would be able to combine two side effects in a single expression. For this purpose Racket provides the form Sbegin. This operator will take any number of expressions and will sequentially evaluate them, i.e., one after the other, returning the value of the last. Logically, if only the value of the last expression is used, then all other values from the other expressions are discarded and these are only relevant for the side effect they may have produced.

Using the begin operator we can further detail our function:

(define (circle-square p r inscribed?)
  (if inscribed?
    (begin
      "create a circle"
      "create a square inscribed in the circle")
    (begin
      "create a circle"
      "create a square circumscribed in the circle")))

All we need to do now is translate each expressions into the corresponding functions. In the case of the inscribed square we will use polar coordinates because we know its vertices will be set in a circle, one with \(\frac{\pi}{4}\) and another at \(\pi+\frac{\pi}{4}\).

(define (circle-square p r inscribed?)
  (if inscribed?
    (begin
      (circle p r)
      (rectangle (+pol p r (* 5/4 pi)) (+pol p r (* 1/4 pi))))
    (begin
      (circle p r)
      (rectangle (+xy p (- r) (- r)) (+xy p r r)))))

Note that for the if operator, both the consequence and the alternative are one single expression even though each of those expressions is the result of two other more elemental expressions. The begin operator can therefore be seen as a mechanism for grouping expressions.

Even though sequencing expressions requires the use of the operator begin, it is usually possible to minimize its use in these expressions. An attentive look at the previous function circle-square shows that a circle is always created independently of the square being inscribed or circumscribed. That way we can redefine the function so that the circle is created outside the if:

(define (circle-square p r inscribed?)
  (begin
    (circle p r)
    (if inscribed?
      (begin
        (rectangle (+pol p r (* 5/4 pi)) (+pol p r (* 1/4 pi))))
      (begin
        (rectangle (+xy p (- r) (- r)) (+xy p r r))))))

It is now clear that the two begins of the if form only have one expression each so its use is unnecessary. We can therefore simplify the expression to:

(define (circle-square p r inscribed?)
  (begin
    (circle p r)
    (if inscribed?
      (rectangle (+pol p r (* 5/4 pi)) (+pol p r (* 1/4 pi)))
      (rectangle (+xy p (- r) (- r)) (+xy p r r)))))

Even tough the operator begin is necessary every time we wish to evaluate more than one expression, there are some situations where Racket uses an implicit begin, such as in a function’s body. In fact, when a function is invoked every expression in the function’s body is evaluated sequentially and the function’s value is only determined by the last expression. This fact allows us to simplify the circle-square function even more:

(define (circle-square p r inscribed?)
  (circle p r)
  (if inscribed?
    (rectangle (+pol p r (* 5/4 pi)) (+pol p r (* 1/4 pi)))
    (rectangle (+xy p (- r) (- r)) (+xy p r r))))
3.5.1 Exercises 15
3.5.1.1 Question 50

Define a function called circle-and-radius that given the coordinates of its centre point and the radius creates the specified circle and, as shown in this figure, places the text describing the radius on the circle’s right side. The text should be proportional to the circle’s size.

3.5.1.2 Question 51

Using the previously defined function circle-and-radius, recreate this figure.

3.6 Doric Order

The Doric Order exemplified in the Greek Temple of Segesta. This temple was never ended Photography by Enzo De Martino.

image

In This figure we see an image of the Segesta Greek temple. This temple, which was never finished, was built during the 5th century D.C, and represents a fine example of the Doric order, the oldest of the three orders of Greek architecture. In this order a column is composed of a shaft, an Echinus and an abacus. The abacus is shaped like a squared plaque that stands on top the Echinus, the Echinus is similar to an inverted cone frustum which stands on top the shaft, and the shaft is similar to a cone frustum with twenty flutes around it. These flutes are semi-circular shaped and carved along the shaft.

These columns also present an intentional deformation called entasis. The entasis is a small curvature give to columns and is believed to be a way a creating a optical illusion in order to misguide the eye to see the column curved instead of straight.

When the Romans adopted the Doric order they introduced some modifications, in particular to the flutes which, in many cases, were simply removed.

Even though we are particularly interested in three.dimensional modelling, for pedagogic reasons, we are going to start by sketching a Doric column (without flutes) in two dimensions. In the following sections we will extend this process to create a three-dimensional model.

The same way a Doric columns can be decomposed into its basic components - shaft, Echinus and abacus - the drawing of one can too be decomposed in components. Therefore, we will create functions to draw the shaft, Echinus and abacus. this figure shows a reference model. Let us start by defining a function for the shaft.

The Doric column for reference

(define (shaft)
  (line (xy -0.8 10)
        (xy -1 0)
        (xy 1 0)
        (xy 0.8 10)
        (xy -0.8 10)))

In this example, we used the line function that, given a sequence of positions, will create a line with vertices on those positions. Another possibility, probably more appropriate, would be to create a closed polygonal line, something we can do with the polygon function, omitting the repeated position, i.e.:

(define (shaft)
  (polygon (xy -0.8 10)
           (xy -1 0)
           (xy 1 0)
           (xy 0.8 10)))

To complete the figure we also need a function for the Echinus and another for the abacus. The reasoning for the Echinus is similar:

(define (echinus)
  (polygon (xy -0.8 10)
           (xy -1 10.5)
           (xy 1 10.5)
           (xy 0.8 10)))

For the abacus we could use a similar strategy or explore a function in Rosetta that creates rectangles. This function requires two points to define a rectangle:

(define (abacus)
  (rectangle (xy -1 10.5)
             (xy 1 11)))

Finally, the expression that creates a Doric column:

(define (doric-column)
  (shaft)
  (echinus)
  (abacus))

Note that the doric-column function sequentially calls the functions shaft, Echinus and finally abacus.

This figure shows the result of invoking the doric-column function.

A Doric column.

3.7 Parametrization of Geometric Figures

Unfortunately, the Doric column we created has fixed position and size, making it difficult to use this function in different contexts. Naturally, this function would be much more useful if its creating was parametrized, i.e., if its creation depended on the parameters that describe a column, as for example, the column’s base coordinates, the height of the Echinus, shaft and abacus, the base and top Echinus radius, etc.

In order to better understand this parametrization of these functions, let us start by considering the shaft represented in this figure.

Sketch of a column’s shaft.

The first step in parametrizing a geometrical drawing is to correctly identify the relevant parameters. In the case of the shaft one of the obvious parameters would be its spatial location, i.e., the coordinates of a reference point in relation to which the shaft will be drawn. Let us then consider that the shaft will be placed with its base’s centre point at an imaginary point \(P\) of arbitrary coordinates \((x,y)\). In addition to this parameter we also need know the height of the shaft \(a\) and both base \(r_b\) and top radius \(r_t\).

To make the drawing process easier it is convenient to consider additional reference points in our sketch. For the shaft, since it’s shaped essentially like a trapeze, we can look at its shape as the succession of line segments along a sequence of points \(P_1\), \(P_2\), \(P_3\) and \(P_4\), points which we can easily calculate from \(P\).

We now have all we need to define a function that draws the column’s shaft. To make the program clearer we will use the names a-shaft for the height \(a\), the base radius r-base and top radius r-top respectively. The definition will now be:

(define (shaft p a-shaft r-base r-top)
  (polygon (+xy p (- r-top) a-shaft)
           (+xy p (- r-base) 0)
           (+xy p (+ r-base) 0)
           (+xy p (+ r-top) a-shaft)))

Next we need to draw out the Echinus. It is convenient to consider once more a geometrical sketch, as shown in this figure.

Sketch of an Echinus.

As similar to the shaft, from the bases’ centre point \(P\) we can compute the coordinates that define the extremities of the lines that draw an Echinus. Using those points, the function will be:

(define (echinus p a-echinus r-base r-top)
  (polygon (+xy p (- r-base) 0)
           (+xy p (- r-top) a-echinus)
           (+xy p (+ r-top) a-echinus)
           (+xy p (+ r-base) 0)))

Having done the shaft and Echinus all that is left now is to draw the Abacus. For that we can consider yet another geometric sketch, shown in this figure.

Sketch of an Abacus.

Once more we will consider \(P\) as the starting point in the Abacus’s base. From this point we can easily calculate the points \(P_1\) and \(P_2\), that are the two opposite corners of the rectangle that defines the Abacus. That way, we have:

(define (abacus p a-abacus l-abacus)
  (rectangle (+xy p (/ l-abacus -2) 0)
             (+xy p (/ l-abacus 2) a-abacus)))

Finally, to create the entire column we must combine the functions that draw the shaft, the Echinus and the Abacus. We need only to take into account that, as this figure shows, the shaft’s top radius is coincidental with the Echinus’ base radius and the Echinus top radius is half the width of the Abacus. The figure also shows that the coordinates of the Abacus’ base is the result of adding the shaft’s height to the the coordinates of the the shaft’s base and that the Abacus’s base coordinates are the result of adding the combined heights of the shaft and Echinus to the shaft’s base coordinates.

Composition of the shaft, Echinus and Abacus.

As we did before, let us give more appropriate names to the parameters in this figure. Using the names p, a-shaft, r-base-shaft, a-Echinus, r-base-Echinus, a-abacus and l-abacus instead of the corresponding, \(P\), \(a_f\), \(r_{bf}\), \(a_c\), \(r_{bc}\), \(a_a\) and \(l_a\), we obtain:

(define (column p
                a-shaft r-base-shaft
                a-echinus r-base-echinus
                a-abacus l-abacus)
  (shaft p a-shaft r-base-shaft r-base-echinus)
  (Echinus (+xy p 0 a-shaft) a-echinus r-base-echinus (/ l-abacus 2))
  (abacus (+xy p 0 (+ a-shaft a-echinus)) a-abacus l-abacus))

Using this function we can easily explore different variations of columns. The following examples reproduce what is shown in this figure.

(column (xy  0 0) 9 0.5 0.4 0.3 0.3 1.0)
(column (xy  3 0) 7 0.5 0.4 0.6 0.6 1.6)
(column (xy  6 0) 9 0.7 0.5 0.3 0.2 1.2)
(column (xy  9 0) 8 0.4 0.3 0.2 0.3 1.0)
(column (xy 12 0) 5 0.5 0.4 0.3 0.1 1.0)
(column (xy 15 0) 6 0.8 0.3 0.2 0.4 1.4)

Multiple Doric columns.

As we can see, not all columns obey the proportion’s canon of the Doric order. Further ahead we are going to see which modifications are needed to avoid this problem.

3.8 Documentation

In the column function, a-shaft is the shaft’s height, r-base-shaft is the shaft’s base radius, r-top-shaft is the shaft’s top radius, a-echinus is the Echinus’ height and, finally, r-abacus is the Abacus’ radius. Because the function already employs many parameters and its meaning may not be clear to someone that reads the function’s definition for the first time, it is convenient to document the function. For that, Racket provides a special syntax: each time a semicolon ; is used Racket will ignore everything that is in front of it until the end of that line. That allows text to be written between the code without Racket trying to interpret its meaning.

Using documentation, the full program for creating Doric columns will look something like what is shown below. Note that this example tries to show the different ways documentation can be used in Racket and not a specific example of a documented program.

The program is so simple in fact that it should not require so much documentation.

;;;;Drawing Doric Columns

 

;;;The drawing of a Doric column is divided into three parts:

;;;shaft, Echinus and abacus. Each of those parts has an

;;;independent function.

 

;Draws the shaft of a Doric column.

;p: column's centre base coordinate,

;a-shaft: shaft's height,

;r-base: shaft's base radius,

;r-top: shaft's top radius.

(define (shaft p a-shaft r-base r-top)

  (polygon (+xy p (- r-top) a-shaft)

           (+xy p (- r-base) 0)

           (+xy p (+ r-base) 0)

           (+xy p (+ r-top) a-shaft)))

 

;Draws the Echinus of a Doric column.

;p: Echinus centre base coordinate,

;a-echinus: Echinus height,

;r-base: Echinus base radius,

;r-top: Echinus top radius.

(define (Echinus p a-Echinus r-base r-top)

  (polygon (+xy p (- r-base) 0)

           (+xy p (- r-top) a-echinus)

           (+xy p (+ r-top) a-echinus)

           (+xy p (+ r-base) 0)))

 

;Draws the Abacus of a Doric column.

;p: column's centre base coordinate,

;a-abacus: abacus height,

;l-abacus: abacus width.

(define (abacus p a-abacus l-abacus)

  (rectangle (+xy p (/ l-abacus -2) 0)

             (+xy p (/ l-abacus +2) a-abacus)))

 

;Draws a Doric column composed of a shaft, an Echinus and an Abacus.

;p: column's centre base coordinate,

;a-shaft: shaft's height,

;r-base-shaft: shaft's base radius,

;r-base-echinus: Echinus base radius = shaft's top radius,

;a-echinus: Echinus height,

;a-abacus: abacus height,

;l-abacus: abacus width = 2*Echinus top radius.

(define (column p

                a-shaft r-base-shaft

                a-echinus r-base-echinus

                a-abacus l-abacus)

  ;;We draw the shaft at the base point p

  (shaft p a-shaft r-base-shaft r-base-echinus)

  ;;We place the Echinus on top of the shaft

  (Echinus (+xy p 0 a-shaft) a-echinus r-base-echinus (/ l-abacus 2))

  ;;and the Abacus on top of the Echinus

  (abacus (+xy p 0 (+ a-shaft a-echinus)) a-abacus l-abacus))

When a program is documented, is easier to understand what the program ~does, without having to study the functions’ body. As we can see in the following example, it is common pragmatic in Racket to use a different number of semicolons to indicate the importance of the comment:

It is important that we get used to documenting our programs but it is important to note that excessive documentation can also have disadvantages:

For these reasons, we should try to write the code as clear as possible and at the same time provide short and useful documentation: the documentation should not say what is already obvious from reading the program.

3.8.1 Exercises 16
3.8.1.1 Question 52

Consider an arrow with an origin in \(P\), a length of \(\rho\), an inclination \(\alpha\) , an opening angle \(\beta\) and a tip \(\sigma\), as shown in the following figure:

Define a function arrow that, given the parameters \(P\), \(\rho\), \(\alpha\), \(\beta\) and \(\sigma\) creates the corresponding arrow.

3.8.1.2 Question 53

Based on the previous exercise define a function that given the point \(P\), the length \(\rho\) and the angle \(\alpha\) draws "the North" as shown in the following figure:

This drawing should also obey the following constraints:
  • The opening angle \(\beta\) must be \(45\,^{\circ}.\)

  • The arrow’s tip length \(\sigma\) must be \(\frac{\rho}{2}\).

  • The "N" should be distanced \(\frac{\rho}{10}\) from the arrow’s tip in the arrow’s direction

  • The size of "N" should be half the length \(\rho\).

3.8.1.3 Question 54

Using the function arrow function, define a new function called arrow-from-to that given two points, creates an arrow that goes from the first point to the second. coordinate. The tips should have one unit as measurement and the angle should be \(\frac{\pi}{8}\).

3.8.1.4 Question 55

Consider a house with only rectangular rooms. Define the function rectangular-divisions that receives as parameters the bottom left corner of a room, the length and width of the room and a text describing that room’s type of use. With those values, that function should build a rectangle with two text lines, one with the rooms type of use and the other with the room’s floor area. For example, the following code lines:

(rectangular-division (xy 0 0) 4 3 "kitchen")
(rectangular-division (xy 4 0) 2 3 "pantry")
(rectangular-division (xy 6 0) 5 3 "room")
(rectangular-division (xy 0 5) 5 4 "living-room")
(rectangular-division (xy 5 5) 3 4 "toilet")
(rectangular-division (xy 8 3) 3 6 "room")

They produce as a result the following drawing:

3.9 Debugging

As we all know Errare humanum est. Making mistakes is part of our day-to-day life and we mostly learn how to deal with them. The same does not apply to programming languages. Any mistake made in a computer program will result in it having a different behaviour than that which was to be expected.

Seeing how easy it is to make errors, it should also be easy to detect and correct them. The process of detecting and correcting errors is called debugging. Different programming languages provide different mechanisms to do that. As we will see Racket is well covered for these mechanisms.

Generally, errors in programs can be classified as syntactic errors or semantic errors.

3.9.1 Syntactic Errors

Syntactic errors occur each time we write a line of code which does not follow the language’s grammar rules. As an example let us suppose we wish to create a function to create a single Doric column, which we will refer as standard, and that always has the same dimensions, dispensing the need for any other parameters. One possible definition is:

(define (standard-column
  (column (xy 0 0) 9 0.5 0.4 0.3 0.3 0.5)))

However, if we test this definition, Racket will produce an error, telling us that something is wrong:

define: not an identifier,identifier with default,or keyword for
procedure argument in: (column (xy 0 0) 9 0.5 0.4 0.3 0.3 0.5)

The error Racket is warning us about is that the form define does not follow the required syntax, and in fact a careful look at it shows we have not followed the correct syntax that, as we saw in section Defining Functions, should obey the following structure:

(define (name parameter1 ... parametern)
  body)

Our mistake is now obvious, we forgot to close the parentheses in the list of parameters. That makes Racket use the following expression as part of the parameters list and, being unable to do so, Racket reports a syntactic error, i.e., a "sentence" which does not obey the language syntax.

There are several other types of errors that Racket is capable of detecting and these will be discussed as we go along. The important thing is not that what kind of syntactic errors Racket is capable of detecting but rather that Racket is capable of checking the expressions we write and detect syntactic errors in them before evaluating them.

3.9.2 Semantic Errors

Semantic errors are very different from syntactic ones. A semantic error is not the misspelling of a "sentence" but rather a mistake in its meaning. In other terms, a semantic error occurs when we write a sentence which we think has a certain meaning but in reality it has a different one.

Generally semantic errors can only be detected during the evaluation of the functions that contain them. Part of the semantic errors are detectable by Racket’s evaluator but countless others can only be detected by the programmer.

As an example of a semantic error consider a meaningless operation such as the sum of a number with a string:

> (+ 1 "two")

+: contract violation

  expected: number?

  given: "two"

  argument position: 2nd

  other arguments...:

   1

As we can see the mistake is explained in the given message, which says that the second argument should be a number. In this example the mistake is obvious enough for us to detect it immediately. However, with more complex programs the process of finding mistakes can often be slow and frustrating. It is also a fact that the more experienced we get at detecting errors the faster we will be at doing it..

3.10 Three-dimensional Modelling

As you have seen in the previous section, Rosetta provides multiple drawing tools (lines, rectangles, circles, etc.) that allows us to easily draw bi-dimensional representations of objects such as floor sections, side sections, etc.

Although to this moment we have only used Rosetta’s bi-dimensional drawing capacities it is possible to go further and start exploring three-dimensional modelling. This type of modelling represents lines, surfaces and volumes in the three-dimensional space.

In this section we will study Rosetta’s functions the allow the creating of such three-dimensional objects.

3.10.1 Predefined Solids

Rosetta provides a set of predefined functions that create solids from their three-dimensional coordinates. Although these predefined functions only allow the creation of a limited number of solids they are enough to create sophisticated models.

The predefined operations can create boxes (function box), cylinders (function cylinder), cones (function cone), spheres (function sphere), toruses (function torus) and pyramids (function regular-pyramid). Each of these functions accepts various optional arguments for creating solids in these solids in different ways. this figure shows a set of solids built from the following expressions:

(box (xyz 2 1 1) (xyz 3 4 5))
(cone (xyz 6 0 0) 1 (xyz 8 1 5))
(cone-frustum (xyz 11 1 0) 2 (xyz 10 0 5) 1)
(sphere (xyz 8 4 5) 2)
(cylinder (xyz 8 7 0) 1 (xyz 6 8 7))
(regular-pyramid 5 (xyz -2 1 0) 1 0 (xyz 2 7 7))
(torus (xyz 14 6 5) 2 1)

Primitive solids in Rosetta.

Great Pyramid of Giza. Photography by Nina Aldin Thune.

image

It is interesting to notice that some of the most important works in architectural history could be modelled directly using these operations. One example is the Great Pyramid of Giza, this figure, built over forty five centuries ago and an almost perfect example of a square pyramid. In its original form, the Great Pyramid had a square base, with sides measuring 230 meters, and a height of 147 meters which made it the tallest man-made structure up until the construction of the Eiffel Tower. Many other Egyptian pyramids have similar proportions which makes them easy to model with the following function:

(define (egyptian-pyramid p side height)
  (regular-pyramid 4 p (/ side 2) 0 height #f))

The case of the Great Pyramid of Giza would be:

(egyptian-pyramid (xyz 0 0 0) 230 147)

Contrary to the Great Pyramid of Giza, there are not many examples that can be modelled with a single geometric primitive. It is however possible to create more complex structures by combining these primitives.

Take for example the cross in this figure, built using six identical truncated cones, with their base positioned at the same point and the top vertices positioned on orthogonal axis.

Cross, overlapping truncated cones.

To model this solid we will parametrize the base point \(p\) and the other cone’s dimensions : the base radius \(r_b\), top radius \(r_t\) and length \(c\):

(define (cross p rb rt c)
  (cone-frustum p rb (+x p c) rt)
  (cone-frustum p rb (+y p c) rt)
  (cone-frustum p rb (+z p c) rt)
  (cone-frustum p rb (+x p (- c)) rt)
  (cone-frustum p rb (+y p (- c)) rt)
  (cone-frustum p rb (+z p (- c)) rt))
3.10.2 Exercises 17
3.10.2.1 Question 56

Using the function cross determine approximate values for the parameters as to create the following models:

3.10.2.2 Question 57

Using the function cone-frustum define the function hourglass function that given the base centre point, the base radius, the neck radius and the height, creates the model of a hourglass similar to the following example:

Similar to the truncated cone, but with a polygonal base, we have the truncated pyramid. A truncated pyramid can be created using the function regular-pyramid-frustum. For that we must specify the number of sides, the base centre point, the radius of the circle that circumscribes the base, the base rotation angle, the top centre point and the radius of the circle that circumscribes the top.

this figure you can see the rotation’s angle effect given by the following expressions:

Three truncated pyramids with different rotation angles. From left to right the angles are: \(0\), \(\frac{\pi}{4}\) e \(\frac{\pi}{2}\).

(regular-pyramid-frustum 3 (xyz 0 0 0) 2 0 (xyz 0 0 1) 1)
(regular-pyramid-frustum 3 (xyz 8 0 0) 2 pi/4 (xyz 8 0 1) 1)
(regular-pyramid-frustum 3 (xyz 16 0 0) 2 pi/2 (xyz 16 0 1) 1)

The Dahshur pyramid. Photography by Ivrienen.

image

For a more architectural example let us consider the Dahshur rhomboidal pyramid, illustrated in this figure, characterized for having sloped edges that abruptly change from \(43^{\circ}\) to \(55^{\circ}\), presumably to avoid its collapse due to the original slope. This pyramid is \(188.6\) meters wide and \(101.1\) meters tall and it can be decomposed into two geometrical forms, an initial truncated pyramid onto of which stands a quadrangular pyramid.

In order to generalise the modeling of this type of pyramid we can consider the schematic model in This figure where a section of this pyramid is shown. From there it is easy to see that \(h_0 + h_1 = h\) and that \(l_0+l_1=l\). Moreover we have the trigonometric functions

\[\tan\alpha_0=\frac{h_0}{l_0}\qquad\qquad \tan\alpha_1=\frac{h_1}{l_1}\]

As a result, we have

\[l_0=\frac{h-l\tan\alpha_1}{\tan\alpha_0-\tan\alpha_1}\]

Section of a rhomboidal pyramid.

Transcribing these functions into Racket we have:

(define (rhomboidal-pyramid p l h a0 a1)
  (define l0 (/ (- h (* l (tan a1))) (- (tan a0) (tan a1))))
  (define l1 (- l l0))
  (define h0 (* l0 (tan a0)))
  (define h1 (- h h0))
  (regular-pyramid-frustum 4 p l 0 h0 l1)
  (regular-pyramid 4 (+z p h0) l1 0 h1))

The rightmost model in This figure shows a Dahshur pyramid created by the following expression:

(rhomboidal-pyramid (xyz 0 0 0) (/ 186.6 2) 101.1
                   (radians<-degrees 55) (radians<-degrees 43))

In the same image, to the left, two other pyramids were created with the following expressions:

(rhomboidal-pyramid (xyz 300 0 0) (/ 186.6 2) 101.1
                   (radians<-degrees 75) (radians<-degrees 40))
(rhomboidal-pyramid (xyz 600 0 0) (/ 186.6 2) 101.1
                   (radians<-degrees 95) (radians<-degrees 37))

Three different rhomboidal pyramids.

Washington Monument. Photography by David Iliff.

image

3.10.3 Exercises 18
3.10.3.1 Question 58

An obelisk is a monument shaped like a rhomboidal pyramid. The Washington Monument, this figure, is a modern example of an obelisk of enormous size, which can be defined, (relative to this figure) with a truncated pyramid \(2l=16.8\) meters wide on the bottom and \(2l_1=10.5\) meters wide on top, a total height of \(169.3\) meters and an upper pyramid top height of \(h_1=16.9\) meters.

Define the obelisk function that given the base centre point, base width, base height, top width and total height, creates an obelisk.

3.10.3.2 Question 59

A perfect obelisk follows a set of proportions in which its height is equal to the pyramid’s base width, which in turn is one-tenth of the total height. The top width has to be two thirds of the base width. With these proportions in mind, define the perfect-obelisk function, that given a base centre point and the total height, creates a perfect obelisk.

3.10.3.3 Question 60

Using the regular-pyramid-frustum function, define a function called prism that creates a regular prismatic solid. The function should receive as parameters the number of sides, the three-dimensional coordinates of the base’s centre point, the distance between that centre point to each vertices, the base rotation angle and the three-dimensional coordinates of the top’s centre point. As examples consider the following expressions:

(prism 3 (xyz  0  0 0) 0.4 0 (xyz  0  0 5))
(prism 5 (xyz -2  0 0) 0.4 0 (xyz -1  1 5))
(prism 4 (xyz  0  2 0) 0.4 0 (xyz  1  1 5))
(prism 6 (xyz  2  0 0) 0.4 0 (xyz  1 -1 5))
(prism 7 (xyz  0 -2 0) 0.4 0 (xyz -1 -1 5))
which produce the following image:

3.10.3.4 Question 61

The Sears Tower shown in This figure (today called Willis Tower was for many years the tallest building in the world. This tower consists of nine square prisms, with different heights \(h_{i,j}\) connected to each other.

The Sears Tower, em Chicago.

image

From a top view these nine blocks define a square with a side \(l\), as shown in the following sketch:

Using the function prism defined in the previous exercise, define a function called sears-tower capable of creating buildings similar to the Sears Tower. The function should have as parameters the three-dimensional coordinates of the base corner \(P\), the base width \(l\) and nine more parameters relative to each height value \(h_{0,0}, h_{0,1},\ldots,h_{2,2}\).

The real Sears Tower has the following parameters: \(l=68.7 m\), \(h_{0,1}=h_{1,1}=442 m\), \(h_{1,0}=h_{2,1}=h_{1,2}=368 m\), \(h_{0,0}=h_{2,2}=270 m\) and \(h_{0,2}=h_{2,0}=205 m\) as shown in the following image:

Besides the already presented geometrical primitives there is another that allows the creation of cuboid solids, i.e., solids with six faces but with a shape that is not necessarily cubic. A cuboid is defined by its eight vertices divided into two sets of four, respectively the base and top vertices (in counter-clockwise order. The following expressions produce the three cuboids presented in this figure:

Three cuboid solids with different vertexes

(cuboid (xyz 0 0 0) (xyz 2 0 0) (xyz 2 2 0) (xyz 0 2 0)
        (xyz 0 0 2) (xyz 2 0 2) (xyz 2 2 2) (xyz 0 2 2))
 
(cuboid (xyz 4 0 0) (xyz 5 0 0) (xyz 5 2 0) (xyz 4 2 0)
        (xyz 3 1 2) (xyz 5 1 2) (xyz 5 2 2) (xyz 3 2 2))
 
(cuboid (xyz 7 2 0) (xyz 8 0 0) (xyz 8 3 0) (xyz 6 3 0)
        (xyz 7 2 2) (xyz 8 0 2) (xyz 8 3 2) (xyz 6 3 2))

The John Hancock Center, in Chicaco. %Fotografia de User:Cacophony

image

The John Hancock Center, in this figure, is a good example of a building with a geometry that can be modelled with a cuboid. In fact this building has a truncated shape with regular base and top. To model it we can can start by defining the regular-cuboid-geometry function parametrized by the base centre point \(P\), the base length \(c_b\) and width \(l_b\), the top base length \(c_t\) and width \(l_t\) and finally by the height \(h\). For creating the solid the cuboid function becomes particularly useful, leaving us only with determining the position each vertex relative to the base point \(P\).

(define (regular-cuboid-geometry p cb lb ct lt h)
  (cuboid (+xyz p (/ cb -2) (/ lb -2) 0)
          (+xyz p (/ cb 2) (/ lb -2) 0)
          (+xyz p (/ cb 2) (/ lb 2) 0)
          (+xyz p (/ cb -2) (/ lb 2) 0)
          (+xyz p (/ ct -2) (/ lt -2) h)
          (+xyz p (/ ct 2) (/ lt -2) h)
          (+xyz p (/ ct 2) (/ lt 2) h)
          (+xyz p (/ ct -2) (/ lt 2) h)))

Using this function, it becomes trivial to create buildings inspired by the shape of the John Hancock Center, as presented in this figure.

Buildings inspired by John Hancock Center’s cuboid geometry.

Pylons of the Karnak Temple. Illustration by Albert Henry Payne.

image

3.10.3.5 Question 62

A pylon is a distinctive Egyptian architectural element, illustrated in this figure. It is a monumental gateway enclosed by two identical tapering towers on both sides. Each tower is cuboid shaped, with a rectangular base and top and the remaining sides as trapeziums. Define a conveniently parametrized function capable of creating a simplified version of a pylon, similar to the one shown in the following image:

3.11 Cylindrical Coordinates

We have seen in previous sections some examples of the use of rectangular and polar coordinate systems. It also became clear that a rightful choice of coordinate system can simplify a geometric problem greatly.

For three-dimensional modelling, besides the rectangular and polar, it is also common to use two other coordinate systems: cylindrical coordinates and spherical coordinates.

As we can see in figure this figure, a point in cylindrical coordinates is defined by a radius \(\rho\) radius on the \(z=0\) plan, an angle \(\phi\) with the \(x\) axis and a height \(z\). It is easy to see that the radius and the angle match the polar coordinates of a point’s projection on the \(z\) plan.

Cylindrical coordinates.

From this figure we can easily see that given a point \((\rho, \phi, z)\) in cylindrical coordinates, that same point in rectangular coordinates would be \[(\rho \cos \phi, \rho \sin \phi, z)\].

Likewise, a point in rectangular coordinates \((x,y,z)\) would be represented in in cylindrical coordinates as \[(\sqrt{x^2+y^2},\arctan\frac{y}{x}, z)\]

These equivalences are assured by the constructor of cylindrical coordinates cyl. Although this function is pre-defined in Rosetta it is not difficult it is defined as

(define (cyl ro fi z)
  (xyz (* ro (cos fi))
       (* ro (sin fi))
       z))

If we simply want to add to a point a translation in cylindrical coordinates, we can use the +cyl function, which makes use of the +xyz function:

(define (+cyl p ro fi z)
  (+xyz p
        (* ro (cos fi))
        (* ro (sin fi))
        z))
3.11.1 Exercises 19
3.11.1.1 Question 63

Define the selectors cyl-rho, cyl-phi and cyl-z which return, respectively, the components \(\rho\), \(\phi\) and \(z\) of a point built with the constructor cyl.

3.11.1.2 Question 64

Define a function stairs capable of building a helix staircase. The following image shows three different examples of helix staircases:

It can be seen that a staircase is formed by a central cylinder onto which \(10\) cylindrical steps are connected. For easier testing consider the central cylinder as a radius of \(r\). Each step is equal to the central cylinder, they measure \(10\) times the centre pole radius and are placed at incremental heights. Each is distanced \(h\) from the previous one and, seen from a top view, makes with it an angle \(\alpha\).

The function stairs should have as parameters the coordinates of the central cylinder’s base, the radius \(r\), the height \(h\) and the the angle \(\alpha\). As an example, consider that the stairs in the previous figure were created with the following expressions:

(stairs (xyz 0 0 0) 1.0 3 (/ pi 6))
(stairs (xyz 0 40 0) 1.5 5 (/ pi 9))
(stairs (xyz 0 80 0) 0.5 6 (/ pi 8))

3.12 Spherical Coordinates

As we can see in this figure, a point in spherical coordinates (also called polar) is characterized by the measurement of the radius \(\rho\), an angle \(\phi\) (called longitude or azimuth) whose projection onto the \(z=0\) plane makes with the \(x\) axis an angle \(\psi\) (also called colatitude, zenith or emph{polar angle}) with the \(z\) axis.

Colatitude is the complementary angle to the latitude, i.e., the the difference between the pole (\(\frac{\pi}{2}\)) and the latitude.

Spherical Coordinates

Given a point \((\rho, \phi, \psi)\), that same point in spherical coordinates is \[(\rho \sin \psi \cos \phi, \rho \sin \psi \sin \phi, \rho \cos \psi)\]

Likewise a point \((x,y,z)\) in Cartesian coordinates, that same point in spherical coordinates is \[(\sqrt{x^2+y^2+z^2},\arctan\frac{y}{x}, \arctan\frac{\sqrt{x^2+y^2}}{z})\]

As it happens with cylindrical coordinates, the constructors of spherical coordinates sph and +sph are already pre-defined but it is not difficult to deduce their definitions:

(define (sph ro fi psi)
  (xyz (* ro (sin psi) (cos fi))
       (* ro (sin psi) (sin fi))
       (* ro (cos psi))))
 
(define (+sph p ro fi psi)
  (+xyz p
        (* ro (sin psi) (cos fi))
        (* ro (sin psi) (sin fi))
        (* ro (cos psi))))
3.12.1 Exercises 20
3.12.1.1 Question 65

Define the selectors sph-rho, sph-phi and sph-psi which return, respectively, the components \(\rho\), \(\phi\) and \(\psi\) of a point built with the constructor sph.

3.12.1.2 Question 66

The Mohawk hairstyle was widely used during the punk period. It is defined by styling the hair in the shape of a crest, as shown below:

Define the function mohawk, with parameters \(P\), \(r\), \(c\), \(\phi\) and \(\Delta_\psi\), which creates 9 cones of length \(c\) and base radius \(r\), all centred at point \(P\), leaning with an angle \(\Delta_\psi\) between them and placed along a plane with an angle \(\phi\) with the \(XZ\) plane.

An example is presented below as a result of the following expressions:

(mohawk (xyz 0  0 0) 1.0 10 (/ pi 2) (/ pi 6))
(mohawk (xyz 0 15 0) 0.5 15 (/ pi 3) (/ pi 9))
(mohawk (xyz 0 30 0) 1.5 6 (/ pi 4) (/ pi 8))

3.13 Modelling Doric Columns

The three-dimensional modelling has the virtue of allowing us to create geometrical entities that are more realistic than mere agglomerates of lines representing views of that entity. As an example, reconsider the Doric column we introduced back in Doric Order. In that section we developed a series of functions capable of creating a front view of each of that column’s components. Even though those views are useful it is even more useful to model a column directly as a three-dimensional entity.

In this section we are going to employ some of the most relevant operations for three-dimensional modelling of columns, in particular truncated cones for shaping the shaft and the Echinus and rectangular box to shape the Abacus.

Before, our "columns" were laid out in the \(XY\) axis with them "growing" along the \(Y\) axis. Now, only the column’s base will be set on the \(XY\) plane: the column’s body will grow along the \(Z\) axis. Although it would be trivial to employ a different arrangement of axes, this is the one closest to reality.

Similarly to many other functions in Rosetta, each of the operations to model solids has different ways of being called upon. For the case of modelling truncated cones - cone-frustum - the method to us more convenient is that one that receives the base centre coordinates, the base radius, height and finally the top radius.

With this in mind we can redefine the operation for creating the column’s shaft:

(define (shaft p h-shaft r-base r-top)
  (cone-frustum p r-base h-shaft r-top))

Likewise, the operation for creating the Echinus will become:

(define (echinus p h-echinus r-base r-top)
  (cone-frustum p r-base a-echinus r-top))

Finally, to build the Abacus - the rectangular box at the column’s top - we have many ways of specifying it. One way is to specify the two corners of this box. The other is to specify one of these corners followed by the box’s dimensions. For this example we will employ the second alternative.

(define (abacus p h-abacus l-abacus)
  (box (+xyz p (/ l-abacus -2) (/ l-abacus -2) 0)
       l-abacus
       l-abacus
       h-abacus))
3.13.1 Exercises 21
3.13.1.1 Question 67

Implement the abacus function but using the other option for creating a rectangular box whit the two corners.

Finally all there is left is to implement the column function that, similar to what happened in the bi-dimensional case, successively invokes the functions shaft, Echinus and abacus but now progressively increasing the \(Z\) coordinate:

(define (column p
                h-shaft r-base-shaft
                h-echinus r-base-echinus
                h-abacus l-abacus)
  (shaft p h-shaft r-base-shaft r-base-echinus)
  (echinus (+z p h-shaft) a-echinus r-base-echinus (/ l-abacus 2))
  (abacus (+z p (+ h-shaft h-echinus)) h-abacus l-abacus))

With these redefinitions we can now repeat the columns drawn in section Doric Order, and shown in this figure, but now creating a three-dimensional image as presented in this figure:

(column (xyz  0 0 0) 9 0.5 0.4 0.3 0.3 1.0)
(column (xyz  3 0 0) 7 0.5 0.4 0.6 0.6 1.6)
(column (xyz  6 0 0) 9 0.7 0.5 0.3 0.2 1.2)
(column (xyz  9 0 0) 8 0.4 0.3 0.2 0.3 1.0)
(column (xyz 12 0 0) 5 0.5 0.4 0.3 0.1 1.0)
(column (xyz 15 0 0) 6 0.8 0.3 0.2 0.4 1.4)

Multiple three-dimensional columns.

3.14 Vitruvian Proportions

The method for modelling Doric columns that we developed in the previous section allows us to easily build columns, for which we need only indicate the values for the relevant parameters, such as the shaft’s height and base radius, the Echinus’ height and base radius and the Abacus’ height and width. Each of these parameters represents a degree of freedom that we can freely vary.

Even though it is logic to think that the more degrees of freedom we have the more flexible the modelling is, the truth is an excessive number of parameters will often lead to unrealistic models. That phenomenon can be seen in this figure where we show a a set of columns with randomly chosen parameters.

Three-dimensional columns with randomly chosen parameters. Only one of these columns obeys the canon of the Doric order.

In fact, according to the canons of the Doric Order, the different parameters that regulate the shape of a column should relate to each other following a set of well defined proportions. Vitruvius, in his famous architectural treatise, considers that these proportions derive directly from the proportions of the human body.

Vitruvius was a Roman writer, architect and engineer during the 1st century b.C., and the author of the only architectural treatise that has survived from Ancient times.

Because they wanted to raise a temple with columns but did not know the adequate proportions, [...], they measured a man’s foot and saw that is was one sixth of his height, so they gave the column a similar proportion, i.e., they made its height, including the capital, six times the column’s width, measured from the base. Thus the Doric order obtained its proportion and its beauty from the male human figure.

Vitruvius characterizes the Doric order in terms of modules:

These considerations lead us to determine the values of some of the parameters to draw a column, in terms of the shaft’s base radius. In terms of implementing this, that means that the function’s parameters will now be local variables whose value is attributed by applying the proportions established by Vitruvius to the the parameter r-base-shaft. The function is thus defined:

(define (doric-column p r-base-shaft r-base-echinus)
  (define h-shaft (* 13 r-base-shaft))
  (define h-echinus (* 2/3 r-base-shaft))
  (define h-abacus (* 1/3 r-base-shaft))
  (define l-abacus (* 13/6 r-base-shaft))
  (shaft p h-shaft r-base-shaft r-base-echinus)
  (echinus (+z p h-shaft) h-echinus r-base-echinus (/ l-abacus 2))
  (abacus (+z p (+ h-shaft h-echinus)) h-abacus l-abacus))

or, alternatively:

(define (doric-column p r-base-shaft r-base-echinus)
  (let ((h-shaft (* 13 r-base-shaft))
        (h-echinus (* 2/3 r-base-shaft))
        (h-abacus (* 1/3 r-base-shaft))
        (l-abacus (* 13/6 r-base-shaft)))
    (shaft p h-shaft r-base-shaft r-base-echinus)
    (echinus (+z p h-shaft) h-echinus r-base-echinus (/ l-abacus 2))
    (abacus (+z p (+ h-shaft a-echinus)) h-abacus l-abacus)))

Using this function it is now possible to create columns closer to the Doric proportions (as set by Vitruvius). this figure shows the result of evaluating the following expressions:

(doric-column (xyz  0 0 0) 0.3 0.2)
(doric-column (xyz  3 0 0) 0.5 0.3)
(doric-column (xyz  6 0 0) 0.4 0.2)
(doric-column (xyz  9 0 0) 0.5 0.4)
(doric-column (xyz 12 0 0) 0.5 0.5)
(doric-column (xyz 15 0 0) 0.4 0.7)

Variations of Doric columns according to Vitruvius’ proportions.

Vitruvius’ proportions allowed us to reduce the number of independent parameters for a Doric column to only two: the shaft’s base radius and Echinus’ base radius. However, it does not seem right for these parameters to be completely independent since that allows bizarre columns to be constructed, where the shaft’s top is larger than the base, as it happens in the rightmost column in this figure.

In truth, the characterization of the Doric Order that we presented is incomplete since, for the column’s proportions, Vitruvius also added:

The reduction at the top of a column seems to be regulated according to the following principles: if a column is less than fifteen feet, let the base width be divided into six parts and use five of those and let five of those parts be used to form the top’s width. If the column has between fifteen and twenty feet, let base width be divided into six and a half parts, and let five and a half of those parts be used for the upper width of the column. If a column has between twenty to thirty feet let the base width be divided into seven parts and let the reduced top measure six of them. A column of thirty to forty feet should be divided at the base into seven and a half parts, and, at the beginning of the reduction, it should have six and a half of these parts at the top. Columns of forty to fifty feet should be divided into eight parts, and reduced to seven of those at the top of the column under the capital. In the case of higher columns, let the reduction be determined proportionally, on the same principles. (In Vitruvius, The Ten Books on Architecture, III book, Chapter 3.1)

These considerations by Vitruvius allow us to determine the ratio between the top and bottom of a column in terms of its height in feet.

A Foot was the fundamental unit of measurement during for several centuries but its value has changed along the years. The measurement of the international foot is \(304.8\) millimetres and was established in 1958. Before that, many other measurements were used, as the Doric foot of \(324\), the Roman and Ionic feet of \(296\) millimetres, the Athenian foot of \(315\) millimetres, the Egyptian and Phoenician feet of \(300\) millimetres, etc.

Let us then consider a function, which we will call shaft-top-radius, that receives as parameters the column’s base width and the column’s height and returns as the result the width for the column’s top width.

A literal translation of Vitruvius’ considerations allows us to write:

(define (shaft-top-radius base-radius height)
  (cond ((< height 15) (* (/ 5.0 6.0) base-radius))
        ...))

The previous fragment obviously corresponds to the statement: "if a column is less than fifteen feet, let the base width be into six parts and use five of those and let five of those parts be used to form the top’s width." In case the column has less than fifteen feet we skip to the next statement: "If the column has between fifteen and twenty feet, let base width be divided into six and a half parts, and let five and a half of those parts be used for the upper width of the column". Translating this last statement we have:

(define (shaft-top-radius base-radius height)
  (cond ((< height 15)
         (* (/ 5.0 6.0) base-radius))
        ((and (>= height 15) (< height 20))
         (* (/ 5.5 6.5) base-radius))
        ...))

A careful analysis of the two previous statements shows that, in reality, we are making excessive tests on the second clause. In fact, if we can get to the second clause that means the first one is false, i.e., a height is not less than 15 and, therefore, it is higher or equal to 15. In that case it is useless to test if the height is higher of equal to 15 again. That way we can simplify the function and write instead:

(define (shaft-top-radius base-radius height)
  (cond ((< height 15) (* (/ 5.0 6.0) base-radius))
        ((< height 20) (* (/ 5.5 6.5) base-radius))
        ...))

Moving on with the translation, leads us to:

(define (shaft-top-radius base-radius height)
  (cond ((< height 15) (* (/ 5.0 6.0) base-radius))
        ((< height 20) (* (/ 5.5 6.5) base-radius))
        ((< height 30) (* (/ 6.0 7.0) base-radius))
        ((< height 40) (* (/ 6.5 7.5) base-radius))
        ((< height 50) (* (/ 7.0 8.0) base-radius))
        ...))

The problem now is that Vitruvius as let the door open to arbitrarily high columns, simply saying: "In the case of higher columns, let the reduction be determined proportionally, on the same principles". To clearly understand the principles we speak of let us consider the evolution of the relation between the top and base of the columns which is visible on the side image.

The ration between the column’s top radius and the base radius is as shown in the side image (something which was also already clear in the shaft-top-radius function), a sequence like

\[\frac{5}{6},\, \frac{5\frac{1}{2}}{6\frac{1}{2}},\, \frac{6}{7},\, \frac{6\frac{1}{2}}{7\frac{1}{2}},\, \frac{7}{8},\, \cdots{}\]

It now becomes obvious that, for higher columns, "the same principles" Vitruvius speaks of come down to, for each \(10\) additional feet, adding \(\frac{1}{2}\) to both the numerator and the denominator. However, it is important to notice that this principle should only be applied if the height exceeds \(15\) feet, since the first interval is bigger than the other ones. Thus, we have to handle columns up to \(15\) feet differently and, from there onward, simply subtract \(20\) feet from the height and determine integer division by \(10\) in order to know how many times we need to add \(\frac{1}{2}\) to both the numerator and the denominator of \(\frac{6}{7}\).

It is the "handle differently" for one case and the other which suggests the need to come up with a selection mechanism: it is necessary to distinguish between two cases and react to each accordingly. For Vitruvius’ column, if the column has a height \(a\) up to \(15\) feet, the ration between the top and the base is \(r=\frac{5}{6}\); if the height \(a\) is not less than \(15\) feet, the ration between the top and the base shall be:

\[r=\frac{6 + \lfloor\frac{a-20}{10}\rfloor\cdot\frac{1}{2}}{7 + \lfloor\frac{a-20}{10}\rfloor\cdot\frac{1}{2}}\]

As an example, let us consider a column with \(43\) feet. The integer division of \(43-20\) by \(10\) is \(2\) so we must add \(2\cdot{}\frac{1}{2}=1\) to the numerator and \(\frac{6}{7}\) to the denominator, and we will get \(\frac{7}{8}=0.875\).

As as for a second example let us consider the proposal made by Adolf Loos for the headquarters of the Chicago Tribune journal, a \(122\) meter-tall building with the shape of a doric column on top of a large base. The column alone would be \(85\) meters hight. Taking into account that a foot, in the Doric Order, measured \(324\) millimetres, the column would have \(85/0.324\approx 262\) feet. The integer division of \(262-20\) by \(10\) is \(24\). So the ratio between the top and the base of this hypothetical would then be \(\frac{6+24/2}{7+24/2}=\frac{18}{19}=0.95\). Because the value is close to the unit it shows that the column would be practically cylindrical.

Based on these considerations we can now define a function that, given an integer representing the column’s height in feet, computes the ration between the top and the base. Beforehand, however, it is convenient to simplify the formula for columns with heights not inferior to \(15\) feet. So:

\[r=\frac{6 + \lfloor\frac{a-20}{10}\rfloor\cdot\frac{1}{2}}{7 + \lfloor\frac{a-20}{10}\rfloor\cdot\frac{1}{2}}= \frac{12 + \lfloor\frac{a-20}{10}\rfloor}{14 + \lfloor\frac{a-20}{10}\rfloor}= \frac{12 + \lfloor\frac{a}{10}\rfloor - 2}{14 + \lfloor\frac{a}{10}\rfloor - 2}= \frac{10 + \lfloor\frac{a}{10}\rfloor}{12 + \lfloor\frac{a}{10}\rfloor}\]

The function’s definition would then be:

(define (shaft-top-radius base-radius height)
  (if (< height 15)
      (* 5/6 base-radius)
      (let ((divisions (quotient height 10)))
        (* (/ (+ 10 divisions)
              (+ 12 divisions))
           base-radius))))

This was the last expression that was missing in order to completely specify the drawing of a Doric column according to Vitruvius in his architectural treatise. Let us consider that we will supply the coordinates for the column’s base centre point and its height. All remaining parameters will be calculated in terms of these ones. The definition will be:

(define (doric-column p height)
  (define r-base-shaft (/ height 14))
  (define r-base-echinus (shaft-top-radius r-base-shaft height))
  (define a-shaft (* 13 r-base-shaft))
  (define a-echinus (* 2/3 r-base-shaft))
  (define a-abacus (* 1/3 r-base-shaft))
  (define l-abacus (* 13/6 r-base-shaft))
  (shaft p a-shaft r-base-shaft r-base-echinus)
  (echinus (+z p a-shaft) a-echinus r-base-echinus (/ l-abacus 2))
  (abacus (+z p (+ a-shaft a-echinus)) a-abacus l-abacus))

The following expressions produce the result shown in this figure:

Note that the column’s height should be given specified in feet.

(doric-column (xy 0 0) 10)
(doric-column (xy 10 0) 15)
(doric-column (xy 20 0) 20)
(doric-column (xy 30 0) 25)
(doric-column (xy 40 0) 30)
(doric-column (xy 50 0) 35)

Column variations according to Vitruvius proportions.

Finally it is worth mentioning that the functions column and doric-column represent two extreme cases: the first one models a column with many degrees of freedom, from the position to the measurements of the shaft, echinus and abacus, whereas the second only allows the position and height to be given. The function doric-column is in fact a particular case of function column so it can be defined in terms of it:

(define (doric-column p height)
  (define r-base-shaft (/ height 14))
  (define r-base-echinus (shaft-top-radius r-base-shaft height))
  (define a-shaft (* 13 r-base-shaft))
  (define a-echinus (* 2/3 r-base-shaft))
  (define a-abacus (* 1/3 r-base-shaft))
  (define l-abacus (* 13/6 r-base-shaft))
  (column p
          a-shaft r-base-shaft
          a-echinus r-base-echinus
          a-abacus l-abacus))

The functions column and doric-column are also a good example of a modelling strategy. Whenever possible, we should begin by defining the most general and unconstrained case, contemplating the highest number of degrees of freedom that are reasonable, and only then should we consider the particular cases, modelled with specific functions but which can naturally resort to the definition of the general case.

3.14.1 Exercises 22
3.14.1.1 Question 68

A careful look at the shaft-top-radius function shows there is a repeated fragment of code, namely the multiplication by the base-radius parameter. This suggests that it should be possible to come up with an even more compact version of this function. Define it.

4 Recursion

4.1 Introduction

We have seen previously that our functions, in order to do something useful, must call other functions. For example, if we already have the function that computes the square of a number and if we want to define the function that computes the cube of a number, we can easily do it by using the square and an additional multiplication, i.e.:

(define (cube x)
  (* (square x) x))

Similarly, we can define the function fourth-power in terms of the cube function and an additional multiplication:

(define fourth-power (x)
  (* (cube x) x))

Obviously, we can continue to define new functions to compute larger powers but this is a lengthy process and, moreover, it will always be limited. It would be much more useful if we were able to generalize this process and simply define the exponentiation function which, from two numbers (base and exponent) computes the first one raised to the power of the second one.

However, what we did for the fourth-power, the cube and the square gives us an important clue: if we have a function that computes the exponentiation with the immediately lower exponent, then we only need one additional multiplication to compute the exponentiation with the next exponent.

In other words, we have:

(define (power x n)
  (* (inferior-power x n) x))

Although we were able to simplify our power calculation problem, there is still one unanswered question: how can we calculate the power immediately below? The answer may not be obvious but once understood, it is trivial: the exponentiation immediately lower to the exponentiation of exponent n is the exponentiation to the power of \(n-1\). That implies that (inferior-power x n) is exactly the same as (power x (- n 1)). Based on this idea, we can rewrite the previous definition:

(define (power x n)
  (* (power x (- n 1)) x))

In spite of our ingenious solution this definition has a problem: regardless of the exponentiation we try to compute, we will never be able to get the a final result. In order to understand this problem it is simpler to consider a real case: let us try to calculate the third power of the number \(4\), i.e., (power 4 3).

For this, according to the power function definition, we will need to evaluate the following expression:

(* (power 4 2) 4)
\(\downarrow\)
(* (* (power 4 1) 4) 4)
\(\downarrow\)
(* (* (* (power 4 0) 4) 4) 4)
\(\downarrow\)
(* (* (* (* (power 4 -1) 4) 4) 4) 4)
\(\downarrow\)
(* (* (* (* (* (power 4 -2) 4) 4) 4) 4) 4)
\(\downarrow\)
(* (* (* (* (* (* (power 4 -3) 4) 4) 4) 4) 4) 4)

It is obvious that this process will never finish. The problem is due to the fact that we reduced the power calculation of a number raised to an exponent to the power calculation of this number raised to an exponent immediately below it, but we have not said in which situation we already have a simple enough exponent to which the solution is immediate. Which is the situation where this happens? We have seen that when the exponent is \(2\) the square function returns the correct answer so the case \(n = 2\) is sufficiently simple. However, it is possible to have an even simpler case: when the exponent is \(1\), the result is simply the base value. Finally, the simplest case of all: when the exponent is \(0\), the result is \(1\), regardless of the base value. This last case is easy to understand when we see that the evaluation of (power 4 2) (i.e., the forth power of four) is reduced to (* (* (power 4 0) 4) 4). For this expression to be similar to (* 4 4) it is necessary that the evalauation of (power 4 0) produces 1.

We are now capable of defining the power function correctly:

(define (power x n)
  (if (zero? n)
    1
    (* (power x (- n 1)) x)))

The previous example is an example of a recursive function, i.e., it is a function which is defined in terms of itself. In other words, a recursive function is a function that calls itself inside its own definition. This use is obvious when we "unwrap" the evaluation process for the (power 4 3):

(power 4 3)
\(\downarrow\)
(* (power 4 2) 4)
\(\downarrow\)
(* (* (power 4 1) 4) 4)
\(\downarrow\)
(* (* (* (power 4 0) 4) 4) 4)
\(\downarrow\)
(* (* (* 1 4) 4) 4)
\(\downarrow\)
(* (* 4 4) 4)
\(\downarrow\)
(* 16 4)
\(\downarrow\)
64

The recursion is the mechanism that allows a function to call upon itself during its own evaluation process. Recursion is one of the most important programming tools, so it is important we understand it well. Many apparently complex problems usually have surprisingly simple recursive solutions.

There are countless examples of recursive functions. One of the simplest is the factorial function that is defined mathematically as:

\[n!= \begin{cases} 1, & \text{if $n=0$}\\ n \cdot (n-1)!, & \text{otherwise} \end{cases}\]

The translation of this formula into Racket is straightforward:

(define (factorial n)
  (if (zero? n)
     1
     (* n (factorial (- n 1)))))

It is important to notice that for all recursive functions there is:

If we analyse the factorial function, the stopping criterion is the equality test to zero (zero? n), the immediate result is 1, and the recursive case is obviously (* n (factorial (- n 1))).

Usually, a recursive function is only correct if it has a conditional statement which identifies the basic case but this needs not be mandatory. Invoking a recursive function consists of successively solving simpler sub-problems until the simplest case of all is reached, for which the result is immediate. This way, the most common pattern to write a recursive function is:

Given this pattern, the most common errors associated with recursive functions are:

Note that a recursive function that works perfectly for the cases for which it was created can be completely wrong for other cases. The factorial function is an example: when the argument is negative, the problem’s complexity increases and becomes further away from the basic case:

(factorial -1)
\(\downarrow\)
(-1 * (factorial -2))
\(\downarrow\)
(-1 * (-2 * (factorial -3)))
\(\downarrow\)
(-1 * (-2 * (* -3 (factorial -4))))
\(\downarrow\)
(-1 * (-2 * (-3 * (* -4 (factorial -5)))))
\(\downarrow\)
(-1 * (-2 * (-3 * (* -4 (* -5 (factorial -6))))))
\(\downarrow\)

The most frequent error in a recursive function is when it never stops, either because the basic case in not correctly detected or, because the recursion does not decrease the problem’s complexity. In this case, the number of recursive calls grows indefinitely until the computer’s memory is exhausted. At this point, the program generates an error message. In Racket’s case this error is not entirely obvious because the evaluator only interrupts the evaluation, showing no results. Here is an example:

> (factorial 3)
6
> (factorial -1)
ERROR

It is very important to correctly understand the concept of recursion. Although, at first, it may be difficult to embrace fully its implications, recursion allows solving with great simplicity, apparently very complex problems.

4.1.1 Exercises 23
4.1.1.1 Question 69

The Ackermann function is set to non-negative numbers as follows: \[A(m, n) = \begin{cases} n+1 & \text{if $m = 0$} \\ A(m-1, 1) & \text{if $m > 0$ and $n = 0$} \\ A(m-1, A(m, n-1)) & \text{if $m > 0$ and $n > 0$}\end{cases}\] Define the Ackermann function in Racket.

4.1.1.2 Question 70

What is the value of:
  • (ackermann 0 8)

  • (ackermann 1 8)

  • (ackermann 2 8)

  • (ackermann 3 8)

  • (ackermann 4 8)

4.2 Recursion in Architecture

As we will see, in architecture recursion is also a fundamental concept. As an example, let us consider a ladder profile, as outlined in this figure and let us imagine that we intend to define a function called ladder that, given the point \(P\), the length \(c\) of the thread and the \(e\) height of each riser, and finally the number of steps \(n\), creates the stairs with the first riser starting at \(P\). Given these parameters, the definition of the function should start as:

(define (ladder p c e n)
  ...)

Profile of a ladder with \(n\) steps, with the fist step starting at the point \(P\), each step containing a thread \(c\) and a riser \(e\).

To implement this function we have to be able to decompose the problem in less complex sub problems and this is where recursion provides a great help: it allows us to decompose the drawing of a ladder with \(n\) steps into the drawing of a step followed by the drawing of a ladder with \(n-1\) steps, as presented in the diagram of this figure.

Decomposition of the design of a ladder of \(n\) steps into the design of a ladder of \(n-1\) steps. steps.

This means the function will be something like:

(define (ladder p c e n)
  ...
  (step p c e)
  (ladder (+xy p c e) c e (- n 1)))

To draw a step we can define the following function that creates the segments for the thread and riser:

(define (step p c e)
  (line p (+y p e) (+xy p c e)))

The problem now is that the ladder function needs to stop creating steps at some point. It is easy that when successively reducing the number of steps, that moment comes when we reach a point where that number is zero. Thus, when asked to draw a ladder with zero steps, the ladder function no longer needs to do anything. This means that the function should have the following form:

(define (ladder p c e n)
  (if (= n 0)
    ...
    (begin
      (step c e)
      (ladder (+xy p c e) c e (- n 1)))))

What is left to decide is what does the function produces as output when it reaches the stopping condition. As, in this case, what interests us is the side effect resulting from invoking the function, it is less relevant what it produces as a result so we will stipulate that it will produce #t to indicate all worked out well:

(define (ladder p c e n)
  (if (= n 0)
    #t
    (begin
      (step p c e)
      (ladder (+xy p c e) c e (- n 1)))))

To see a more interesting example of recursion in Architecture let us consider the Saqqara Pyramid, illustrated in this figure, built by the architect Imhotep in XXVII century b.C.. This step pyramid is considered to be the first pyramid in Egypt and the oldest monumental stone construction in the world, being composed of six progressively smaller mastabas, stacked on top of each other. Another way of looking at this step pyramid is a mastaba on top of which stands another smaller step pyramid.

The Saqqara steps Pyramid. Photography by Charles J. Sharp.

image

Formally, we can define a pyramid of \(n\) steps as a mastaba on top of which stands a pyramid of \(n-1\) steps. To complete this definition it must be said that when the last mastababa is created, the pyramid of \(0\) steps at the top is, actually, non-existent.

Thus, considering the illustration in this figure, if we consider that the centre of the base of the pyramid is the \(p\) position and the mastabas are several pyramid frustums, we can write:

Schematic step pyramid.

(define (step-pyramid p b t h d n)
  (if (= n 0)
    #t
    (begin
      (regular-pyramid-frustum 4 p b 0 h t)
      (step-pyramid (+z p h) (- b d) (- t d) h d (- n 1)))))

An approximate example of the pyramid of Saqqara would then be:

(step-pyramid (xyz 0 0 0) 120 115 20 15 6)

4.2.1 Exercises 24
4.2.1.1 Question 71

The above definition does not accurately reflect the geometry of the step pyramid of Saqqara because it has sloped surfaces between each mastaba, as can be seen in the following diagram where we compare the sections of the pyramid we defined (left) and to the actual pyramid of Saqqara (right):

Define a more rigorous version of the step-pyramid function which receives, in addition to the above parameters, the height of each slope. Experiment with different parameters for you to produce a model similar to the following one:

4.2.1.2 Question 72

The false arch is the oldest form of arch, formed by parallelepipeds arranged horizontally like steps, forming an opening that narrows towards the top, ending with a horizontal beam, as shown in the following diagram:

Assuming that the parallelepipeds have a square section measuring \(l\), the opening reduction is \(\Delta_e\), equal in every step, and that the point \(p\) is the arc’s centre point, define the false-arc function that, with the parameters \(p\), \(c\), \(e\), \(\Delta_e\) and \(l\), creates a false arc.

4.2.1.3 Question 73

Define the balanced-circles function that allows you to create any of the following illustrations:

(balanced-circles (xy 0 0) 1.2 0.3)
(balanced-circles (xy 3 0) 1.2 0.5)
(balanced-circles (xy 6 0) 0.9 0.6)
(balanced-circles (xy 9 0) 0.5 0.8)

Note that the circles have radius that are in a geometrical progression ratio of \(f\), with \(0 <f < 1\). That way, each circle (except the first one) has a radius that is the product of \(f\) by the radius of the largest circle in which it stands. The smallest circle has a radius that is greater or equal to \(1\). The function should have as parameters the centre point and the radius of the larger circle, and also the reduction factor \(f\).

4.2.1.4 Question 74

Consider the drawing of circles as shown in the following image:

Define a radial-circles function that given the coordinates the rotation centre \(p\), the number of circles \(n\), the \(r_0\) translation radius, the circle radius \(r_1\), the initial angle \(\phi\) and the angle increment \(\Delta\phi\), draws the circles as shown in the previous figure.

Test your function with the following expressions:

(radial-circles (xy 0 0) 10 1.5 0.3 0 (/ pi  5))
(radial-circles (xy 4 0) 20 1.5 0.3 0 (/ pi 10))
(radial-circles (xy 8 0) 40 1.5 0.3 0 (/ pi 20))

whose evaluation should generate the following image:

4.2.1.5 Question 75

Consider the design of symbolic flowers composed of an inner circle around which radial circles are arranged corresponding to the petals. These circles should be tangent to each other and to the inner circle, as shown in the following image:

(flower (xy 0 0) 1 10)

Define a flower function that receives only the flower’s centre point, the radius of the inner circle and the number of petals.

Test your function with the following expressions:

(flower (x 0.0) 1.0 10)
(flower (x 3.6) 0.4 10)
(flower (x 8.0) 2.0 20)

Their evaluation should generate the following image:

4.3 Debugging Recursive Programs

We saw in the Debugging section that errors in a program can be classified as syntactic or semantic errors. Syntactical errors occur whenever we write invalid phrases in that language, i.e., phrases that do not obey the grammar rules. Semantic errors are more complex than the syntactic in that they generally can only be detected during the program’s execution.

There are some semantic errors that can be detected before the program’s execution, but this detection depends strongly on the quality of the language implementation and its ability to anticipate the program’s consequences.

For example, if we try to calculate the factorial of a string we will have a semantic error, as shown in the following example:

> (factorial 5)
120
> (factorial "five")
zero?: contract violation
  expected: number?
  given: "five"

Obviously this last error has nothing to do with Racket’s grammar rules: the "sentence" on how to call the factorial function is correct. The problem is that it does not make sense to calculate the factorial of a string, because calculating factorials involves arithmetic operations and these do not apply to strings. Therefore, the error is related to the meaning of the written "sentence", i.e., with the semantics. It is then, a semantic error.

An infinite recursion is another example of a semantic error. We saw that if the factorial function is called with a negative argument, infinite recursion occurs. Consequently, if we use a negative argument, we will be making a semantic error.

Racket provides several mechanisms for detecting errors. One of the simplest ones is the trace form (provided by the racket/trace module) that allows the visualization of a function’s invocations. The trace receives the name of the functions to be analysed and changes these functions so as to write the successive calls with the respective arguments, as well as the result of the its invocation. The information given as the trace result is generally extremely useful for debugging functions.

For example, to visualize the call of the factorial function, consider the following definition:

#lang racket

(require racket/trace)

 

(define (factorial n)

  (if (zero? n)

     1

     (* n (factorial (- n 1)))))

 

(trace factorial)

and the following call:

> (factorial 5)

>(factorial 5)

> (factorial 4)

> >(factorial 3)

> > (factorial 2)

> > >(factorial 1)

> > > (factorial 0)

< < < 1

< < <1

< < 2

< <6

< 24

<120

120

Note that, in the previous example as a consequence of trace, the call of the factorial function appears aligned to the left side. Each recursive call appears slightly to the right, allowing the display of the recursion’s "depth", i.e., the number of recursive calls. The result returned by each call appears aligned in the same column of that call.

4.3.1 Exercises 25
4.3.1.1 Question 76

Trace the power function. What is the resulting trace of the (power 2 10) evaluation?

4.3.1.2 Question 77

Define a circles function capable of creating the illustration presented below:

Note that, the circles have radius that are in geometrical progression ration of \(\frac{1}{2}\). In other words, the smaller circles have half the radius of the adjacent larger circle. The smallest circles of all have a radius greater or equal to \(0.1\). The function should only have as parameters the centre and radius of the larger circle.

4.3.1.3 Question 78

Define the saw function that, given a point \(P\), a number of teeth, the length \(c\) of each tooth and the height \(a\) of each tooth, draws a saw, with the first tooth starting at \(P\) as presented in the following image:

4.3.1.4 Question 79

Define the lozenges function capable of creating the illustration presented below:

(lozenges (xy 0 0) 1.6 0.1)

Note that the dimensions of the lozenges have a geometrical progression ration of \(\frac{1}{2}\). In other words, the smaller lozenges have half the size of the largest lozenges at the tips of which they are centred. The smallest lozenges have a width greater or equal to \(1\). This function should only have as parameters the centre and width of the largest lozenge.

4.3.1.5 Question 80

Consider the stair outlined in the figure below, designed to overcome an slope \(\alpha\).

Define the stair-slope function that receives the point \(p\), the angle \(\alpha\), the length \(c\) and the number of steps \(n\) and builds the ladder described in the previous schema.

4.3.1.6 Question 81

Consider the ladder outlined in the figure below, designed to overcome a slope \(\alpha\).

Note that the steps’ dimensions have a geometric progression ration of \(f\), i.e., given a step with a length of \(c\), the step immediately above has a length of \(f \cdot c\). Define the geometric-progression-ladder function that receives the point \(P\), the angle \(\alpha\), the length \(c\), the number of steps \(n\) and the ration \(f\) and creates the ladder described in the previous schema.

4.4 Doric Temples

With Vitruvius we have seen that the Greeks created an elaborate proportion system for columns. These columns were used to form porticos, wherein a succession of columns crowned with a roof served as the entrance for buildings and, in particular, for temples. When this arrangement of columns was projected from the building it was called prostyle, and it was classified according to the number of columns in its composition as Diastyle, Tristyle, Tetrastyle, Pentastyle, Hexastyle, etc. When the prostyle was extended to the whole building, placing columns all around it, it was called peristyle.

In addition to describing the proportions of columns, Vitruvius also explained in his famous treaty the rules that the construction of temples had to follow, in particular, regarding their orientation, which should be from east to west. and regarding the spacing between columns, distinguishing several cases of temples from those with a very reduced spacing (pycnostyle) to temples with excessively large columns spacing (araeostilo), including the style which he considered to have the best proportion (eustilo) in which the spacing between columns is variable, being larger in the central columns.

To simplify our implementation we will ignore these details and,instead of distinguishing each style, we will simply consider consider the placement of columns distributed linearly in a particular direction, as sketched in this figure.

Temple’s floor plan with an arbitrary orientation

To this moment, we have considered coordinates as mere positions in space. Now, in order to model this temple, it will be useful to adopt a vectorial perspective of the concept of coordinates. In this perspective, coordinates are seen as the tips of vectors positioned at the origin. This can be seen in this figure where we have marked the position of two columns with the vectors \(P\) and \(Q\).

With all vectors being positioned at the origin it becomes irrelevant to mention it, which allows us to characterize vectors as having only a magnitude and a direction. It is easy to see that this magnitude and direction are precisely the polar coordinates of the vector’s end, i.e., the distance from the origin to the tip of the vector and the angle that the vector makes with the \(X\) axis.

The great advantage of adopting the vectorial perspective is that it enables us to conceive an algebra for operating with vectors. For example, the sum of vector is a vector whose components are the sum of corresponding components, i.e., \[P + Q = (P_x + Q_x, P_y + Q_y, P_z + Q_z)\]

Similarly, we can define the subtraction of vectors as \[P - Q = (P_x - Q_x, P_y - Q_y, P_z - Q_z)\] and the multiplication of a vector with a scalar \(\alpha\): \[P\cdot\alpha=(P_x\cdot\alpha,P_y\cdot\alpha,P_z\cdot \alpha)\]

Finally, the division of a vector by a scalar can be defined in terms of the multiplication by the scalar’s inverse value, i.e., \[\frac{P}{\alpha} = P\frac{1}{\alpha}\]

These operations are presented in this figure for the bi-dimensional case.

Algebraic operations using vectors.

Translating these definitions for Racket is straightforward. Since we are creating algebraic operations for coordinates, we shall name them by combining the names of the arithmetic operators with the letter "c" (of "coordinates").

These operations are pre-defined in Racket.

(define (+c p0 p1)
  (xyz (+ (cx p0) (cx p1))
       (+ (cy p0) (cy p1))
       (+ (cz p0) (cz p1))))
 
(define (-c p0 p1)
  (xyz (- (cx p0) (cx p1))
       (- (cy p0) (cy p1))
       (- (cz p0) (cz p1))))
 
(define (*c p a)
  (xyz (* (cx p) a)
       (* (cy p) a)
       (* (cz p) a)))
 
(define (/c p a)
  (*c p (/ 1 a)))
4.4.1 Exercises 26
4.4.1.1 Question 82

A unit vector is a vector of unitary magnitude. Define the unit-vector operation that given any vector calculates the unit vector with the same direction as the given one.

4.4.1.2 Question 83

The symmetric vector of a \(\vec{v}\) vector is the vector \(\vec{v}^\prime\), such that \(\vec{v}+\vec{v}^\prime=0\). In other words, is the vector with equal magnitude but opposite direction. Define the symmetric-vector operation that given any vector calculates the symmetric vector.

Returning to the problem of positioning columns in the temple, illustrated in this figure. Given the orientation and separation vector \(\vec{v}\) of columns, from the position of any column \(P\), we determine the position of the next column through \(P+\vec{v}\). This reasoning allows us to first define a function to create a row of columns. This function will have as parameters the coordinates \(P\) of the base of the first column, the height \(h\) of the column, the vector \(\vec{v}\) separating the axes of the columns and also the number \(n\) of columns that we wish to create. The reasoning behind the definition of this function is, once more, recursive:

The translation of this process to Racket we have:

(define (doric-columns p h v n)
  (if (= n 0)
    #t
    (begin
      (doric-column p h)
      (doric-columns (+c p v)
                     h
                     v
                     (- n 1)))))

We can test the creating of columns using, for example:

(doric-columns (xy 0 0) 10 (xy 5 0) 8)

of which result is presented in this figure.

A perspective of a set of eight Doric columns with \(10\) units of height and \(5\) spacing units between columns along the \(x\) axis.

4.4.1.3 Question 84

Although the use of separation vectors between columns is relatively simple, it is possible to simplify that process even further by calculating the vector value based on the start and end points of the row of columns. Using the doric-columns function, define a function called doric-columns-between that given centre points \(P\) and \(Q\) of the first and final columns, the height \(h\) of the columns and finally the number of columns, creates a row of columns between those two points.

As an example, the following image shows the result of evaluating the following expressions:

(doric-columns-between (pol 10 0.0) (pol 50 0.0) 8 6)
(doric-columns-between (pol 10 0.4) (pol 50 0.4) 8 6)
(doric-columns-between (pol 10 0.8) (pol 50 0.8) 8 6)
(doric-columns-between (pol 10 1.2) (pol 50 1.2) 8 6)
(doric-columns-between (pol 10 1.6) (pol 50 1.6) 8 6)

From the moment we know how to create rows of columns, it becomes easy to create the four necessary rows for building peristyle temples. Normally, the description of these temples is done in terms of the number of columns at the front and at the side, assuming that the columns oat the corners count for both rows. This means that, for example, a temple with \(6 \times 12\) columns there are actually only \(4 \times 2 + 10 \times 2 + 4 = 32\) columns. For creating the peristyle, besides the number of columns at the front and side, we need to know the position of the columns at the extremities of the temple and obviously the column’s height.

In terms of the algorithm, we start by creating one of the corners of peristyle:

(define (doric-peristyle-corner p height v0 n0 v1 n1)
  (doric-columns p height v0 n0)
  (doric-columns (+c p v1) height v1 (- n1 1)))

Note that, in order to avoid repeating columns, the second row must start at the second column and consequentially one less column must be placed.

To build the complete peristyle we only have to create one corner and then build another corner with one less column on each side, progressing in opposite directions.

(define (doric-peristyle p height v0 n0 v1 n1)
  (doric-peristyle-corner p height v0 n0 v1 n1)
  (doric-peristyle-corner
    (+c p (+c (*c v0 (- n0 1)) (*c v1 (- n1 1))))
    height
    (*c v0 -1) (- n0 1) (*c v1 -1) (- n1 1)))

A realistic example is the temple of Segesta, represented in this figure. This temple is of the peristyle type, composed of \(6\) columns (i.e., Hexastyle) at each front and \(14\) columns at the side, a total of \(36\) \(9\) meters high columns. The distance between the columns axes is approximately \(4.8\) meters at the front and \(4.6\) meters at the sides. The expression that creates the peristyle of this temple is then:

(doric-peristyle (xy 0 0) 9 (xy 4.8 0) 6 (xy 0 4.6) 14)

The result of evaluating the above expression is shown in this figure.

An overview of the peristyle of the temple of Segesta. The columns were generated by the peristyle-doric function using as parameters, \(6\) columns on the front and \(14\) on the side, with a column distance of \(4.8\) meters on the front and \(4.6\) meters on the sides, with columns \(9\) meters high.

Although the vast majority of Greek temples were rectangular, circular temples were also built, called Tholos. The Sanctuary of Athena Pronaia at Delphi has a good example of one such buildings. Although little remains of this temple is not difficult to imagine its original shape based on what is still remains, shown in this figure.

The Temple of Pronaia in Delphi, built in IV century b.C.. Photography by Michelle Kelley.

image

To simplify the construction of the Tholos temple, we will divide it into two parts. In one of them we will create the base and on the second one we will position the columns.

For designing the base we can consider a set of flattened cylinders, stacked to form the circular steps, as shown in this figure. Thus, the total base height \(a_b\) will be divided in steps of \(\Delta a_b\) and the base radius will also be divided in \(\Delta r_b\) steps.

A Tholos base section. The base is composed by a sequence of stacked cylinders whose base radius \(R_B\) shrinks \(\Delta r_b\) each step and whose height grows in increments of \(\Delta a_b\) in every step.

For each cylinder we have to consider its radius and the d-height of each step. To draw the next cylinder we have also to consider the radius increment \(d-radius\) due to the step’s length. These steps will be built using a recursive process:

This process is implemented by the following function:

(define (base-tholos p n-steps radius d-height d-radius)
  (if (= n-steps 0)
    #t
    (begin
      (cylinder p radius d-height)
      (base-tholos (+xyz p 0 0 d-height)
                   (- n-steps 1)
                   (- radius d-radius)
                   d-height
                   d-radius))))

For the positioning of columns we will also consider a process wherein at each step we only place one column at a given position and, recursively, we place the remaining columns from the next circular position.

Given its circular structure, the construction of this type of building is simplified by the use of polar coordinates. In fact, we can elaborate a recursive process that, from the peristyle radius \(r_p\) and the initial angle \(\phi\), places a column in that position and then places the other columns using the same radius but increments of \(\Delta\phi\) to \(\phi\), as shown in this figure. The angular increment \(\Delta\phi\) is obtained by dividing the circumference by the \(n\) number of columns to place, i.e., \(\Delta\phi=\frac{2\pi}{n}\). Because the columns are arranged around a circle, the calculation of the coordinates of each column is facilitated with the use of polar coordinates. With this algorithm, in mind the function definition is thus:

Scheme of the construction of a Tholos: \(r_b\) is the base radius, \(r_p\) is the distance from the centre of the columns to the centre of the base, \(a_p\) is the column’s height, \(a_b\) is the base’s height, the \(\phi\) is the initial angle of the columns and \(\Delta\phi\) is the angle between columns.

(define (tholos-columns p n-columns radius fi d-fi height)
  (if (= n-columns 0)
    #t
    (begin
      (doric-column (+pol p radius fi) height)
      (tholos-columns p
                      (- n-columns 1)
                      radius
                      (+ fi d-fi)
                      d-fi
                      height))))

Finally we define the function tholos which, given the necessary parameters to the two previous functions, invokes them sequentially:

(define (tholos p n-steps rb dab drb n-colums rp ap)
  (base-tholos p n-steps rb dab drb)
  (tholos-columns (+xyz p 0 0 (* n-steps dab))
                  n-columns
                  rp
                  0
                  (/ 2pi n-columns)
                  ap))

This figure shows the image generated by the evaluation the following expression:

(tholos (xyz 0 0 0) 3 7.9 0.2 0.2 20 7 4)

Perspective of the Tholos of Athens in Delphi, with \(20\) \(4\) meters high Doric columns, placed within a circle of \(7\) meters radius.

4.4.1.4 Question 85

A closer look to the Tholos in this figure shows that there is an error: the abacus of the various columns are parallel to each other (and also to the abscissas and ordinate axes) when, in fact, they should have a radial orientation. This difference is evident when we compare a top view of the current design (left) with the view of the correct drawing (right):

Redefine the tholos-columns function so that each column is oriented properly in relation to the Tholos centre.

4.4.1.5 Question 86

Consider the construction of a tower made of several modules in which each module has exactly the same characteristics of a Tholos, as presented in the figure below, on the left side:

The top of the tower has a similar shape to the Tholos base but with more steps.

Define the function Tholos-tower that, from the centre of the tower’s base, the number of modules, the top number of steps and other required parameters to define a module identical to the Tholos, builds the tower presented above.

Try to create a tower with \(6\) modules, \(10\) steps at the top, \(3\) steps per module, each with the same length and height \(0.2\), with \(7.9\) base radius and with \(20\) columns per module, with a peristyle radius of \(7\) and with columns measuring \(4\) meters.

4.4.1.6 Question 87

Based on the previous answer, redefine the tower’s construction so that the radial dimension decreases with the height, like the image on the centre of the previous image.

4.4.1.7 Question 88

Based on the previous answer, redefine the tower’s construction so that the number of columns decreases with height, like the image on the right side of the same previous image.

4.4.1.8 Question 89

Consider the creation of a city in space, composed only by cylinders of progressively smaller sizes, joined together by small spheres, as shown in the perspective of the stereoscopic image:

In order to see the stereoscopic image, focus your attention in the middle of the two images and cross your eyes, as if you were to focus on a very close object. You will notice that the two images become four, although slightly blurry. Then try to uncross the eyes in order to see only three images, i.e., until the two central images are overlapped. Concentrate on that overlap and let your eyes relax until the image is focused.

Define a function that starting from the city centre and the central cylinders radius creates a city similar to the presented one.

4.5 Ionic Order

The volute was one of the architectural elements introduced in the transition from the Doric Order to the Ionic Order. A volute is a spiral-shaped ornament placed at the top of a Ionian capital. This figure shows an example of an Ionian capital containing two volutes. Although we still have numerous samples of volutes from the antiquity, their drawing process was never clear.

Volutes of an Ionic capital. Photography by See Wah Cheng.

image

Vitruvius, in his architectural treatise, describes the Ionian volute: a spiral-shaped curve that starts at the base of the abacus, unfolds into a series of turns and joins with a circular element called the eye. Vitruvius describes the spiral drawing process through a composition of fourths of a circumference, starting at the outermost point and decreasing the radius every quarter of a circumference, until joining with the eye. In this description there are still some details to be explained, in particular the position the centres of the fourths of circumference. Vitruvius adds that a calculation and a figure will be added at the end of the book.

Unfortunately such figure and calculation were never found, leaving the drawing process of this fundamental element described by Vitruvius still unclear. The doubts regarding that detail become even more evident when analysis performed on the many volutes that survived the antiquity revealed differences to the proportions described by Vitruvius.

During the Renaissance period, these doubts made researchers rethink Vitruvius’ method and suggesting personal interpretations or new methods for designing volutes. Of particular relevance are the methods proposed in the sixteenth century by Sebastiano Serlio, based on the composition of the semi-circumferences, by Giuseppe Salviati, based on the composition of quarters of a circumferences and by Guillaume Philandrier, based on the composition of eighths of a circumference.

All those methods differ in many details but, generically, they are all based on using arcs of a circumference of constant angles but with a decreasing radius. Obviously, for there to be continuity between the arcs, their centres change as they are being drawn. This figure presents the process of drawing spirals using quarters of a circumference.

Design of a spiral with circular arcs.

As shown in this figure, in order to draw the spiral we must draw successive circumference quarters. The first circumference quarter will be centred at the point \(p\) and have a radius \(r\). This first radius goes from the angle \(\pi/2\) to \(\pi\). The second circumference quarter will be centred at the point \(P_1\) and will have a radius of \(r\cdot f\), with \(f\) being "reduction" factor for the spiral. This second arc goes from \(\pi\) to \(\frac{3}{2}\pi\). An important detail is the relationship between the coordinates of \(P\) and \(P_1\): for the second arc to have its end coincidental with the first arc its centre must be at the end of the vector \(\vec{v}_0\) starting at \(P\), measuring \(r\cdot(1-f)\) and with an angle equal to the final angle of the end of the first arc.

This process should be repeated for the remaining arcs, i.e., we will have to calculate the coordinates \(P_2\), \(P_3\), etc., as well as the radii \(r\cdot f \cdot f\), \(r\cdot f \cdot f \cdot f\), etc., necessary to trace the successive circumference arcs.

When described in such way, the drawing process seems to be complicated. However, it is possible to rewrite it so that it becomes much simpler. In fact, is possible to think of the drawing of the overall spiral as the drawing of a circumference quarter followed by the drawing of a smaller spiral. More accurately, we can specify the drawing of a spiral centred at the point \(P\), radius \(r\) and initial angle \(\alpha\) as a drawing of a circumference arc of radius \(r\), centred at \(P\), with an initial angle of \(\alpha\) and a final angle of \(\alpha + \frac{\pi}{2}\) followed by a spiral centred at \(P+\vec{v}\), with a radius of \(r\cdot f\) and initial angle of \(\alpha + \frac{\pi}{2}\). The vector \(\vec{v}\) will be positioned at \(P\), have a length of \(r\cdot(1-f)\) and an angle \(\alpha + \frac{\pi}{2}\).

Obviously, this being a recursive process, it is necessary to define the stopping condition, and there are (at least) two possibilities:

For now, let’s consider the first possibility. According to the described process, let’s define the function that draws the spiral with the following parameters: the p starting point, the r initial radius, the a initial angle, the n number of quarter circles, and the f reduction factor:

(define (spiral p r a n f)
  (if (= n 0)
    #t
    (begin
      (circumference-quarter p r a)
      (spiral (+pol p (* r (- 1 f)) (+ a pi/2))
               (* r f)
               (+ a pi/2)
               (- n 1)
               f))))

Note that the spiral function is recursive, as it is defined in terms of itself. Obviously, the recursive case is simpler than the original case, since the number of circle quarters is smaller, progressively approaching the stopping condition.

To draw the circumference quarter we will use Racket’s arc operation which receives the circumference centre and radius, and the initial and final angles of the arc. To better understand this spiral drawing process, let us also draw two lines starting at the centre, outlining each circumference quarter. Later, once we have finished developing these functions we will remove them.

(define (circumference-quarter p r a)
  (arc p r a pi/2)
  (line p (+pol p r a))
  (line p (+pol p r (+ a pi/2))))

Now we can try an example:

(spiral (xy 0 0) 3 (/ pi 2) 12 0.8)

The spiral drawn by the expression above is shown in this figure.

The drawn spiral.

The spiral function allows us to define any number of spirals, but with one restriction: each circular arc corresponds to an angle increment of \(\frac{\pi}{2}\). Obviously, this function would be more useful if this increment was also a parameter.

Spiral incremental angle as a parameter.

As can be deduced from observing this figure the required modifications are relatively trivial, there is only the need to add the parameter da, representing the angle increment \(\Delta_\alpha\) of each arc, and replace the occurrences of \(\frac{\pi}{2}\) with this new parameter. Naturally, instead of drawing a quarter of a circle, we will now have to draw a circumference arc of angular amplitude \(\Delta_\alpha\). Seeing as the use of this parameter also affects the meaning of the parameter \(n\), which now will represent the number of arcs with that amplitude, it will advisable to explore a different stopping condition, based on the intended final angle fa. However, we need to be mindful of one detail: the last arc may not be a complete arc if the difference between the final and first angle exceeds the angle increment. In this case the arc will only have this difference for an angle value. The new definition is then:

(define (spiral p r a da fa f)
  (if (< (- fa a) da)
      (spiral-arc p r a (- fa a))
      (begin
        (spiral-arc p r a da)
        (spiral (+pol p (* r (- 1 f)) (+ a da))
                 (* r f)
                 (+ a da)
                 da
                 fa
                 f))))

The function that draws the arc is a generalization of the one that draws one quarter of a circumference:

(define (spiral-arc p r a da)
  (arc p r a da)
  (line p (+pol p r a))
  (line p (+pol p r (+ a da))))

And now, to draw the same spiral as the one represented in this figure, we have to evaluate the following expression:

(spiral (xy 0 0) 1 (/ pi 2) (/ pi 2) (* pi 6) 0.8)

Of course, now we can easily draw other spirals. The spirals produced by the following expressions are shown in this figure:

(spiral (xy 0 0) 2 (/ pi 2) (/ pi 2) (* pi 6) 0.9)
 
(spiral (xy 4 0) 2 (/ pi 2) (/ pi 2) (* pi 6) 0.7)
 
(spiral (xy 8 0) 2 (/ pi 2) (/ pi 2) (* pi 6) 0.5)

Various spirals with different reduction coefficients: \(0.9\), \(0.7\) and \(0.5\), respectively.

Another possibility is to change the angle increment. The following expressions test approximations to Sebastiano Serlio’s approach (semi-circumferences), Giuseppe Salviati’s approach (circumference-quarters) and Guillaume Philandrier’s approach (circumference-eights)

Note that these are mere approximations. The original methods were much more complex.

(spiral (xy 0 0) 2 (/ pi 2) pi (* pi 6) 0.8)
 
(spiral (xy 4 0) 2 (/ pi 2) (/ pi 2) (* pi 6) 0.8)
 
(spiral (xy 8 0) 2 (/ pi 2) (/ pi 4) (* pi 6) 0.8)

The results are shown in this figure.

Several spirals with the coefficient reduction of \(0.8\) and different angle increments: \(\pi\), \(\frac{\pi}{2}\)

Finally, in order to compare the different spirals construction processes, we should adjust the reduction coefficient to the angle increment so that the reduction is applied to one whole turn and not just to the chosen angle increment. Thus, we have the following expressions:

(spiral (xy 0 0) 2 (/ pi 2) pi (* pi 6) 0.8)
 
(spiral (xy 4 0) 2 (/ pi 2) (/ pi 2) (* pi 6) (expt 0.8 1/2))
 
(spiral (xy 8 0) 2 (/ pi 2) (/ pi 4) (* pi 6) (expt 0.8 1/4))

The results are shown in this figure.

Several spirals with the coefficient reduction of \(0.8\) per turn and with different angle increments: \(\pi\), \(\frac{\pi}{2}\) and \(\frac{\pi}{4}\), respectively.

4.5.1 Exercises 27
4.5.1.1 Question 90

The gold spiral is a spiral whose growth rate is \(\varphi\), with \(\varphi=\frac{1 + \sqrt{5}}{2}\) being the golden ratio, popularized by Luca Pacioli in his 1509’s work Divina Proporzione, although there are numerous accounts of its use many years before.

The following drawing illustrates a golden spiral with arcs inscribed in the correspondent golden rectangle, in which one side is \(\varphi\) times bigger than the smaller side.

As can be seen in the previous drawing, the golden rectangle has the remarkable property of being recursively (and infinitely) decomposable into a square and another golden rectangle.

Redefine the spiral-arc function so that, in addition to creating an arc, it also creates the enveloping square. Then write the Racket’s expression to create the golden spiral above.

4.5.1.2 Question 91

An oval is a geometrical figure that corresponds to the outline of birds eggs. Given the variety of egg forms in Nature, is natural to consider that, there are many geometric ovals. The following figure shows some examples where some of the parameters that characterize the oval are systematically changed:

An oval is composed of four circumference arcs as presented in the following image:

The circular arcs needed to draw the oval are defined by the radii \(r_0\), \(r_1\) and \(r_2\). Note that the circular arc of radius \(r_0\) covers an angle of \(\pi\) and the circular arc of radius \(r_2\) covers an angle of \(\alpha\).

Define the oval function that draws an egg. The function should receive as parameters only the coordinates of the point \(P\), the \(r_0\) and \(r_1\) radii and the egg’s height \(h\).

4.5.1.3 Question 92

Define the cylinder-pyramid function that builds a pyramid of cylinders piled on top of each other, as presented in the following image. Note that the cylinders decrease in size (both in length and radius) and suffer a rotation as they are being piled.

4.6 Recursion in Nature

Recursion is present in countless natural phenomena. Mountains, for example exhibit irregularities that when observed at an appropriate scale are identical to ... mountains. A river has tributaries and each tributary is identical to ... a river. A blood vessel has branches and each branch is identical to ... a blood vessel. All these natural entities are examples recursive structures.

A tree is another good example of a recursive structure, since tree branches are as small trees growing from the trunk. As it can be seen in this figure, from each tree branch there are other small trees structures growing from them, a process which repeats itself until a dimensions is sufficiently small for other structures starting to appear, such as leaves, flowers, fruits, pine cones, etc.

The recursive structure of trees. Photography by Michael Bezzina.

image

If, in fact, a tree has a recursive structure then it should be possible to "build" trees with recursive functions. To test this theory let us begin by considering a very simplistic version of a tree, where we have a trunk that from a certain height is divided into two. Each of these sub-trunks grows with an angle from the trunk from which it originated and reaches a certain length that is a fraction of the initial trunk’s length, as shown in this figure. The stopping condition is reached when the length of the trunk become so small that, instead of continuing dividing itself, another structure simply appears. To simplify, let us designate the extremity of a branch with a leaf and we will represent them with a small circle..

Drawing parameters of a tree.

To give some dimension to the tree, let us consider that tree function receives as arguments: the point coordinate \(P\) of the tree base, the length \(c\) of the trunk and the angle \(\alpha\) that it makes with its origin. For the recursive phase, we will have as parameters the opening angle difference \(\Delta_\alpha\) that the new branch should make with the previous one and the reduction coefficient \(f\) for the trunk’s length. The first step is to compute the top of the trunk by using the function +pol. Next we draw the trunk from base to the top. Finally, we test if the drawn trunk is sufficiently small. If it is, we finish with the drawing of a circle centred at top. Otherwise we make a double recursion to draw a sub-tree on the right and another on the left. The definition of the function is then:

(define (tree p c a da f)
  (let ((top (+pol p c a)))
    (branch p top)
    (if (< c 2)
        (leaf top)
        (begin
         (tree top
                 (* c f)
                 (+ a da)
                 da f)
         (tree top
                 (* c f)
                 (- a da)
                 da f)))))
 
(define (branch p0 p1)
  (line p0 p1))
 
(define (leaf p)
  (circle p 0.2))

A first example of a tree, created by the following code

(tree (xy 0 0) 20 (/ pi 2) (/ pi 8) 0.7)

is presented in this figure.

A "tree" measuring \(20\) units, with the initial angle of \(\frac{\pi}{2}\), opening angle of \(\frac{\pi}{8}\) and reduction coefficient of \(0.7\).

this figure presents other examples where the opening angle and the reduction coefficient vary. The sequence of expressions that generated those examples is the following:

(tree (xy 0 0) 20 (/ pi 2) (/ pi 8) 0.6)
 
(tree (xy 100 0) 20 (/ pi 2) (/ pi 8) 0.8)
 
(tree (xy 200 0) 20 (/ pi 2) (/ pi 6) 0.7)

Multiple "trees" with different opening angles and reduction coefficient of the branches.

Unfortunately, the trees presented are "excessively" symmetrical: in nature is literally impossible to find perfect symmetries. For this reason, the model should be a little bit more sophisticated with the introduction of different growth parameters for the branches on the left and on the right. For that, Instead of having a single opening angle and only one length reduction coefficient, we will apply two, as presented in this figure.

Design parameters of a tree with asymmetrical growth.

The tree function transformation to receive the new parameters is trivial:

(define (tree p c a da0 f0 da1 f1)
  (let ((top (+pol p c a)))
    (branch p top)
    (if (< c 2)
        (leaf top)
        (begin
          (tree top
                  (* c f0)
                  (+ a da0)
                  da0 f0 da1 f1)
          (tree top
                  (* c f1)
                  (- a da1)
                  da0 f0 da1 f1)))))

The this figure presents new examples of trees with different opening angles and branches reduction coefficients on both left and right sides, generated by the following expressions:

(tree (xy 0 0) 20 (/ pi 2) (/ pi 8) 0.6 (/ pi 8) 0.7)
 
(tree (xy 80 0) 20 (/ pi 2) (/ pi 4) 0.7 (/ pi 16) 0.7)
 
(tree (xy 150 0) 20 (/ pi 2) (/ pi 6) 0.6 (/ pi 16) 0.8)

Various trees generated with different opening angles and the length reduction coefficients for both left and right sides.

The trees generated by the tree function are only a poor model of reality. Although there are obvious signs that various natural phenomena can be modelled by recursive functions, nature is not as deterministic as our functions. So, in order to make our function closer to reality, it is crucial that some randomness is incorporated. This will be next section’s subject. }

5 State

5.1 Introduction

As we have seen, Racket allows us to establish an association between a name and an entity through the define operator. In this section, we will see that Racket also allows us to change this association, causing the name to be associated with another entity. This operation is called assignment and it is done through the set! operator.

In this section we are going to discuss with more depth the concept of assignment. For motivation we will start by introducing an important topic where assignment plays a key role: randomness.

5.2 Randomness

Drawing involves making conscious decisions that lead to the intended purpose. In this sense, designing appears to be a rational process where there is no room for randomness, luck or uncertainty. In fact, for the drawings we have been doing so far, rationality has been crucial because the computer requires rigorous specification of what is intended, not allowing any ambiguities. However, it is known that a design problem often requires architects to try different solutions before finding the one that pleases them. This experimentation requires some randomness in the process of choosing the drawing "parameters". So, although the final design may provide a structure that reflects the architect’s rational intention, the process that led to this final design is not necessarily rational and may have gone through phases of ambiguity and uncertainty.

When architecture is inspired by nature, an additional factor of randomness arises: in many cases, nature is intrinsically random. That randomness, however, is not total and has restrictions. This fact is easily understood when we consider that, although there are no two equal pine trees, we are all capable of recognizing the pattern that characterizes a pine tree. In all its master pieces, nature combines regularity and randomness. In some cases, such as the growth of crystal, there is more regularity than randomness. In other cases, such as the behaviour of subatomic particles, there is more randomness than regularly.

As in nature, this combination of randomness and regularity is clearly visible in several works of modern architectural. As an example, let us consider two important works by architect Oscar Niemeyer: the Itamaraty Palace and the Mondadori Palace, seen in this figure. Despite the clear resemblances between them the first one excels for the regularity in its arcade, as opposed to the evident randomness that Niemeyer applied in the second one.

Two works by Oscar Niemeyer. The first one, the Itamaraty Palace, designed in 1962. The second one, the Mondadori Palace, designed in 1968. Photographies by Bruno Kussler and Tristan Nitot, respectively.

image image

If we want to use computers to design and this design assumes randomness, then we must be able to incorporate it in our algorithms. Randomness can be incorporated in various ways, for example:

5.3 Random Numbers

In any of the above cases, we can reduce the randomness to choosing numbers within certain limits. For a random colour, we can randomly generate three numbers representing the RGB values for that colour.

RGB stands for Red-Green-Blue, a model where any colour can be seen as the composition of the three primary colours red, green and blue)

For random length, we can generate a random number within that length’s limits. For a logical decision, we can randomly generate an integer between zero and one, making a choice if the number is zero, and another otherwise if the number is one. For a random choice among a set of alternatives, we can simply generate a random number between one and the number of elements of the given set and then choose the alternative corresponding to the randomly generated number.

These examples show us that what is essential is to be able to generate a random number within a certain range. Based on this operation we can implement all the others.

There are two fundamental processes for generating random numbers. The first process is based on the measurement of physical intrinsically random processes, such as electronic noise or radioactive decay. The second process is based on the use of arithmetic functions that, given an initial value (called the seed), produce a sequence of seemingly random numbers, with each number of that sequence having been generated based on the previous generated number. In this case we say it is a pseudo-random number generator. The term pseudo is justifiable since if we repeat the value of the original seed we will also repeat the sequence of generated numbers.

Although a pseudo-random number generator produces a sequence of numbers that are actually not random, it has two important advantages:

Therefore, from now on we will only consider generators of pseudo-random numbers, which we will abusively designate as random number generators. A generator of this kind is characterized by a function \(f\) that, given a certain argument \(x_i\), produces a number \(x_{i+1}=f(x_i)\), apparently unrelated to \(x_i\). The seed of the generator is the element \(x_0\) of the sequence.

What is left now is to find a suitable \(f\) function. For this, and among other qualities, it is required that the numbers generated by \(f\) be equally probable, i.e., all numbers within a certain range are equally probable to be generated and that the period of the sequence of generated numbers is as large as possible, i.e., the sequence of numbers generated only starts repeating after a long time.

In Racket, the modulo operator implements the mathematical operation \(p\bmod q\), corresponding to the remainder of the division of the first operand (the dividend, to the left of the operator) by the second operand (the divisor, to the right of the operator), having the same signal as the divisor. The operator remainder implements the remainder of the division of the dividend by the divisor, but with the same signal of the dividend.

Numerous functions have been studied with these characteristics. The most used is called the linear congruential generator function with the form:

\[x_{i+1} = (a x_i + b)\bmod m\]

For example, if we have \(a = 25173, b = 13849, m = 65536\) and we begin with any seed, for example, \(x_0=12345\), we obtain the following pseudo-random numbers:

2822, 11031, 21180, 42629, 27202, 49667, 50968, 33041, 37566, 43823, 2740, 43997, 57466, 29339, 39312, 21225, 61302, 58439, 12204, 57909, 39858, 3123, 51464, 1473, 302, 13919, 41380, 43405, 31722, 61131, 13696, 63897, 42982, 375, 16540, 25061, 24866, 31331, 48888, 36465,...

Actually, this sequence is not random enough as there is a pattern that repeats continuously. Can you discover it?

We can easily confirm these results using Racket:

(define (next-random-number previous-random-number)
  (modulo (+ (* 25173 previous-random-number) 13849)
          65536))
> (next-random-number 12345)

2822

> (next-random-number 2822)

11031

> (next-random-number 11031)

21180

This approach, however, implies that we can only generate a "random" number \(x_{i + 1}\) if we recall the \(x_i\) number generated immediately before, so that we can provide it as an argument for the next-random-number function. Unfortunately, the moment and the point of the program at which we might need a new random number can occur much later and much further than the moment and the point of the program when the last random number was generated, which substantially complicates the program’s writing. It would be preferable that, instead of having a next-random-number function, that depends on the previous \(x_i\) generated value, we had a random-number function that did not need the previously generated number to be able to produce the next one. This way, at any point of the program where we might need to generate a new random number, all we would have to do was to call the random-number function without having to recall the previously generated number. Starting with the same seed value, we would have:

> (random-number)

2822

> (random-number)

11031

> (random-number)

21180

5.4 State

The random-number function shows a different behaviour from the functions we have seen so far. Until now, all the functions we have defined behaved as traditional mathematical definition: given the arguments, the function produces results and, more importantly, given the same arguments the function always produces the same results. For example, regardless of the number of times the factorial function is called, for a given argument it will always produce the factorial of this argument.

The random-number function is different from the others because besides not needing arguments, it produces a different result each time it is called.

From a mathematical point of view, a function without parameters is not uncommon: it is precisely what is known as a constant. In fact, just as we write \(\sin \cos x\) to designate \(\sin(\cos(x))\), we can just as well write \(\sin \pi\) to designate \(\sin(\pi())\), where \(\pi\) can be seen as a function without arguments.

From a mathematical point of view, a function that produces different results each time is called is anything but normal: is an abomination, since according to the mathematical definition of a function this behaviour is not possible. And yet, this is precisely the type of behaviour we would like the random-number function to have.

To obtain such behaviour is necessary to introduce the concept of state. We say that a function has state when its behaviour depends on its history, i.e., its previous calls. The random-number function is an example of a function with state, but there are countless other examples in the real world. A bank account also has a state that depends on all past transactions. A car’s fuel tank also has state that depends on the fills and past journeys.

For a function to have history it must have memory, i.e., it must recall past events in order to influence future results. So far, we have seen that the define operator allowed us to define associations between names and values, but what has not yet been discussed is the possibility to modify these associations, i.e., change the value that was associated to a particular name. For this, Racket provides the set! operator that given an already defined name and given a new value to associate this name with, removes the previous association and establishes the new one. It is this feature that allows us to include memory in our functions.

In the random-number function’s case, we have seen that the memory that is important to us is what the last random number generated was. Let us thus suppose that we had an association between the name previous-random-number and that number. Initially, this name should be associated with the seed of the random number sequence. for that we could define:

(define previous-random-number 12345)

Next, we can now define the random-number "function" that, not only uses the last value associated with that name, but also updates this association with the new generated value:

(define (random-number)
  (set! previous-random-number
        (next-random-number previous-random-number))
  previous-random-number)
> (random-number)

2822

> (random-number)

11031

As we can see, every time that the random-number function is called, the value associated with previous-random-number is updated, influencing the function’s future behaviour. Obviously, at any given moment, we can re-start the random numbers sequence simply by restoring the seed value:

> (random-number)

21180

> (random-number)

42629

> (set! previous-random-number 12345)
> (random-number)

2822

> (random-number)

11031

> (random-number)

21180

5.5 Random Choices

Observing the next-random-number function definition we find that its last operation is to compute the division’s remainder by \(65536\), which implies that the function always produces values between \(0\) and \(65535\). Although (pseudo) random, these values are contained in a range that will only exceptionally be useful. In fact, it is much more frequent that we need random numbers that are contained in much smaller intervals. For example, if we want to simulate the action of flipping a coin, we are only interested in having the random numbers \(0\) or \(1\), representing "heads" or "tails".

Just as our random numbers are limited to the range \([0,65536[\) by having the remainder of the division by \(65536\), we can once again apply the same operation to produce a smaller range. Therefore, in the case of flipping a coin, we can simply use the random-number function to generate a random number and then apply to it the division remainder by two. In general, when we want random numbers in an interval \([0,x[\), we apply the division remainder by \(x\). Thus we can define a new function that generates a random number between zero and a parameter, and which we will, by tradition, call random:

(define (random x)
  (remainder (random-number) x))

Note that the random function should never receive an argument greater than \(65535\) because that would make the function lose the equiprobability feature of the generated numbers: all numbers greater than \(65535\) will have zero probability of occurring.

In fact, the upper limit should be fairly below the limit of the random function to maintain the equiprobability of the results.

It is now possible to simulate a number of random phenomena such as, for example, flipping a coin:

(define (heads-or-tails)
  (if (= (random 2) 0)
    "heads"
    "tails"))

Unfortunately, when repeatedly tested, our function does not seem very random:

> (heads-or-tails)

"tails"

> (heads-or-tails)

"heads"

> (heads-or-tails)

"tails"

> (heads-or-tails)

"heads"

> (heads-or-tails)

"tails"

> (heads-or-tails)

"heads"

In fact, the results we are getting are a constant repetition of the pair: heads/tails, which shows that the expression (random 2) merely generates the following sequence:

0101010101010101010101010101010101010101010101010101010101

The same phenomenon occurs for other intervals. For example, the (random 4) expression should generate a random number from the set \(\{0,1,2,3\}\) but its repeated invocation generates the following sequence of numbers:

0123012301230123012301230123012301230123012301230123012301

In spite of the numbers perfect equiprobability they are clearly not random.

The problem in both previous sequences is the fact that they have a very small period. The period is the number of generated elements before the cycle is restarted, repeating the same elements previously generated. Obviously, the greater the period the better the random numbers generator will be and therefore the generator that we have shown is of poor quality.

Great amounts of effort have been invested on finding good random number generators and although the better ones are produced using fairly more sophisticated methods than the ones we have used so far, it is also possible to find a good linear congruential generator as long we choose the parameters wisely. In fact, the linear congruential generator \[x_{i+1} = (a x_i + b) \bmod m\] can be a good pseudo-random generator provided we have \(a=16807\), \(b=0\) and \(m=2^{31}-1=2147483647\). A direct translation of the mathematical definition to Racket produces the following function:

(define (next-random-number previous-random-number)
  (modulo (* 16807 previous-random-number)
          2147483647))

Using this new definition, the repeated evaluation of the (random 2) expression produces the following sequence:

01001010001011110100100011000111100110110101101111000011110

and the evaluation of the (random 4) expression produces:

21312020300221331333233301112313001012333020321123122330222

It is fairly clear that the generated sequences now have a period large enough for any repetition pattern to be detected. Thus, from now on, this will be the definition of the next-random-number function that will be used.

5.5.1 Random Fractional Numbers

The process of generating random numbers that we have implemented is only able to generate random integer numbers. However, we often also need to generate fractional random numbers, for example, in the interval \([0,1[\).

In order to fulfil these two requirements it is usual in Racket, that the random function receives the generator’s upper limit \(x\) and analyses it in order to determine whether it is integer or real, returning an appropriate random value in each case. For this we need nothing more than to map the integers’ generator interval, which is as we have seen \([0,2147483647 [\), in the interval \([0,x[\). The implementation is trivial:

This function uses the inexact? universal recognizer which is defined in the Recognizers section.

(define (random x)
  (if (inexact? x)
      (* x (/ (random-number) 2147483647.0))
      (remainder (random-number) x)))
5.5.2 Random Numbers within a Range

If instead of generating random numbers in the range \([0, x[\) we prefer to generate random numbers in the range \([x_0, x_1[\), then we just have to generate a random number within \([x_0, x_1[\) and add \(x_0\). The random-range function implements this behaviour:

(define (random-range x0 x1)
  (+ x0 (random (- x1 x0))))

Like the random function, the random-range function also produces a real value when limits are real numbers.

As an example, let us consider the tree function that models a tree, defined as:

(define (tree p c a da0 f0 da1 f1)
  (let ((top (+pol p c a)))
    (branch p top)
    (if (< c 2)
        (leaf top)
        (begin
          (tree top
                (* c f0)
                (+ a da0)
                da0 f0 da1 f1)
          (tree top
                (* c f1)
                (- a da1)
                da0 f0 da1 f1)))))

To incorporate some randomness in this tree model we can consider that the branches opening angles and the length reduction factors of those branches are not constant values throughout the recursion process, varying within certain limits. Thus, instead of worrying about having different opening angles and factors for the left and right branches, we will simply have random variations on both sides:

(define (tree p c a min-a max-a min-f max-f)
  (let ((top (+pol p c a)))
    (branch p top)
    (if (< c 2)
        (leaf top)
        (begin
          (tree top
                  (* c (random-range min-f max-f))
                  (+ a (random-range min-a max-a))
                  min-a max-a min-f max-f)
          (tree top
                  (* c (random-range min-f max-f))
                  (- a (random-range min-a max-a))
                  min-a max-a min-f max-f)))))

Using this new version we can generate several similar trees yet sufficiently different to seem much more natural. Trees shown in this figure were generated using exactly the same growth parameters:

(tree (xy   0   0) 20 (/ pi 2) (/ pi 16) (/ pi 4) 0.6 0.9)
(tree (xy 150   0) 20 (/ pi 2) (/ pi 16) (/ pi 4) 0.6 0.9)
(tree (xy 300   0) 20 (/ pi 2) (/ pi 16) (/ pi 4) 0.6 0.9)
(tree (xy   0 150) 20 (/ pi 2) (/ pi 16) (/ pi 4) 0.6 0.9)
(tree (xy 150 150) 20 (/ pi 2) (/ pi 16) (/ pi 4) 0.6 0.9)
(tree (xy 300 150) 20 (/ pi 2) (/ pi 16) (/ pi 4) 0.6 0.9)

Multiples trees created with branches opening angles between \([\frac{\pi}{2},\frac{\pi}{16}[\) and length reduction factors within \([0.6,0.9[\).

5.5.3 Exercises 28
5.5.3.1 Question 93

The trees produced by the tree function are unrealistic because they are completely two-dimensional, with trunks that are simple lines and leaves that are small circles. The calculation of the branches’ and leaves’ coordinates is also two-dimensional, relying on polar coordinates given by the width and angle parameters.

It is intended that you redefine the branch, leave and tree functions in order to increase the realism of the generated trees.

To simplify, assume that the leaves can be simulated by small spheres and branches can be simulated by truncated cones with the base radius being \(10\%\) of the branch’s length and top radius being \(90\%\) of the base radius.

For the generation of trees to be truly three-dimensional, redefine the tree function so that the top of each branch is a point in spherical coordinates chosen randomly from the base of the branch. This implies that the tree function, instead of having two parameters for length and angle of the polar coordinates, it will need to have three, for length, longitude and co-latitude. Likewise, instead of receiving the four limits for generating random lengths and angles it will need six limits for the three parameters.

Try different arguments in your redefinition of the tree function, in order to create something similar to the image below:

5.5.3.2 Question 94

Define a function named random-cylinder that receives as argument a number of cylinders \(n\) and that generates \(n\) cylinders placed in random positions, with random radii and lengths, as presented in the following image:

5.5.3.3 Question 95

A random path is a formalization of a particle motion that is subjected to impulses of random intensity from random directions. This phenomenon happens. for example, to a grain of pollen when falling into water: as the molecules of water bump into the grain, it will randomly move.

The following image present three random paths:

Consider a model of a random path in a two-dimensional plane. Define the random-path function that has as parameters the starting point \(p\) of the path, the maximum distance \(d\) that particle can move when pushed and the \(n\) number of successive impulses that the particle will receive. Note that in arch impulse the particle moves in a random direction, a random distance between zero and the maximum distance. This function should simulate the corresponding random path, as presented in the previous figure, which was created with three different executions of this function. From left to right, the following parameters were used: \(d=5, n=100\), \(d=2, n=200\), and \(d=8, n=50\).

5.5.3.4 Question 96

Define a biased heads-or-tails function such that, even though it randomly produces the string "heads" or "tails", "heads" falls only \(30\%\) of the times the function is executed.

5.5.3.5 Question 97

Define the cylinder-sphere function that given a point \(p\), a length \(l\), a radius \(r\) and a number \(n\), creates \(n\) cylinders, of length \(l\) and radius \(r\), which the base centred at \(p\) point, with the top positioned randomly, as presented in the following image:

5.5.3.6 Question 98

Define a function called connected-blocks that is able to build a cluster of connected blocks, i.e., blocks are physically linked together, as presented in the following figure:

Note that the adjacent blocks always have orthogonal orientations.

5.6 Urban Planning

Cities are a good example of the combination between structure and randomness. Although many ancient cities appear to be chaotic as a result of their non planned development, the truth is that since the early ages there was the felt need to structure cities in order to facilitate its use and organize its growth, with well known examples of planed cities dating from \(2600\) BC.

Although there are many different approaches, the two most usual ways of planning a city are through the orthogonal or circular plan. In the orthogonal plan, the avenues are straight lines and make right angles between them. In the circular plan, the main avenues converge into a central square and the secondary avenues develop along concentric circles around this main square, following the city’s growth. This figure presents a good example of a city mainly developed in an orthogonal plan. In this section we will explore randomness in order to produce variations in these plans.

New York’s aerial view in the USA. Photography by Art Siegel.

image

In an orthogonal plan, the city is organized into blocks, with each block assuming a rectangular or square shape and can contain several buildings. To simplify, we will assume that each block will be squared shape and only contains a single building. Each building will have a width determined by the block’s width and an imposed height. The buildings will be separated from each other by streets with a fixed width. The model of this city is presented in this figure.

Diagram of an orthogonal city plan.

In order to structure the function that creates the city with the orthogonal plane let us first decompose the process in the construction of the successive streets. The construction of each street is then be decomposed in the successive construction of buildings. Thus, we must parametrize the function with the number of streets and the number of buildings per street. In addition to that we will need to know the coordinates of the city’s origin point \(p\), the width \(w\) and height \(h\) of each building and finally the width \(s\) of the street. The function will create a row of buildings followed by a street and, recursively, create the remaining rows of buildings and streets corresponding to the next row. To simplify, we will assume that streets are aligned with the \(x\) and \(y\) axes, whereby each new road corresponds to a displacement along the \(y\) axis and each new building corresponds to a displacement along the \(x\) axis. Thus we will have:

(define (buildings-grid p n-streets m-buildings w h s)
  (if (= n-streets 0)
    #t
    (begin
      (street-buildings p m-buildings w h s)
      (buildings-grid (+y p (+ w s))
                     (- n-streets 1)
                     m-buildings
                     w
                     h
                     s))))

For the construction of streets with buildings, the process is the same: we place a building at the correct coordinates and then recursively we place the remaining buildings after the corresponding displacement. The following function implements this process:

(define (street-buildings p m-buildings w h s)
  (if (= m-buildings 0)
    #t
    (begin
      (building p w h)
      (street-buildings (+x p (+ w s))
                   (- m-buildings 1)
                   w
                   h
                   s))))

Finally, we need to define a function that creates a building. To simplify, we modelled it as a parallelepiped:

(define (building p w h)
  (box p w w h))

With these three functions we can now try to build a new urbanization. For example, the following expression creates a set of ten streets with ten buildings per street:

(buildings-grid (xyz 0 0 0) 10 10 100 400 40)

The result is presented in this figure.

An urbanization in an orthogonal grid with a hundred buildings all with the same height.

As it is obvious from this figure the produced urbanization is not very elegant as it lacks some of the randomness that characterizes cities.

To incorporate this randomness we start by considering that the buildings’ height can randomly vary from a maximum height to one-tenth of that height. For this, we redefine the building function:

(define (building p w h)
  (box p
       w
       w
       (* (random-range 0.1 1.0) h)))

With the exact same parameters as before in two consecutive calls of the same expression we can now build more appealing urbanizations shown in this figure and this figure.

An urbanization in an orthogonal grid with a hundred buildings with random heights.

An urbanization in an orthogonal grid with a hundred buildings with random heights.

5.6.1 Exercises 29
5.6.1.1 Question 99

The urbanizations produced by the previous functions do not present sufficient variability as all the buildings have the same shape. To improve the realism and quality of the urbanization, it is intended to set different functions for different types of buildings: the building0 function should build a parallelepiped with random height as before and the building1 function should build a cylindrical tower with a random height, contained within the limits of a block. Define these two functions.

5.6.1.2 Question 100

Use both building0 and building1 functions defined in the previous exercise to redefine the building function so that it randomly constructs different buildings. The resulting urbanization should be composed of \(20\%\) circular towers and \(80\%\) rectangular buildings, as illustrated in the following figure:

View of Manhattan. Photography of James K. Poole.

image

5.6.1.3 Question 101

The cities created in previous exercises only allow the creation of two types of buildings: rectangular and cylindrical buildings. However, when we observe an real city as shown in this figure we find that there are buildings with many other shapes meaning that in order to increase the realism of our simulations we will need to implement a larger number of functions, each one able to create a different building.

Fortunately, a careful observation of this figure shows that, in fact, many of the buildings follow a pattern and can be modelled by overlapping parallelepipeds with random dimensions but always successively smaller depending on the building’s height. This is something that we can easily implement with a single function.

Consider that this building is parametrized by the number of intended "blocks" to overlap, the coordinates of the first block and by the length, width and height of the building. The base block has exactly the specified length and width but its height should be between \(20\%\) and \(80\%\) of the total building height. The following blocks are centred on the block immediately below, with a length and width that are between \(70\%\) and \(100\%\) of the corresponding parameters of the block immediately below. The height of these blocks should be between \(20\%\) and \(80\%\) of the remaining height of the building. The following image shows some examples of buildings that follow this model:

Based on this specification, define the building-blocks function and use it to redefine the building0 function so that the generated buildings have a random number of overlapping blocks. With this redefinition, the buildings-grid function should be able to generate a model similar to the following image, in which for each building the number of blocks varies between \(1\) and \(6\) was used:

5.6.1.4 Question 102

Generally, cities have a core of relatively high buildings in the usually called business centre (abbreviated to CBD, an acronym of Central Business District), and as we move away the height tends to decrease, a phenomenon clearly visible in this figure.

The variation of the buildings height can be modelled by several mathematical functions but in this exercise it is intended that the two-dimensional Gaussian distribution is applied, given by:

\[f(x,y) = e^{-\left( (\frac{x-x_o}{\sigma_x})^2 + (\frac{y-y_o}{\sigma_y})^2\right)}\]

In which \(f\) is the weighting height factor, the \((x_0, y_0)\) are the coordinates of the highest point of the Gaussian surface, and \(\sigma_0\) and \(\sigma_1\) are the factors that determinate the two-dimensional stretch of this surface. In order to simplify, assume that the business centre is located at \((0,0)\) and that \(\sigma_x=\sigma_y=25 l\), with \(l\) being the building’s width. The following image shows an example of one such urbanization:

Incorporate this distribution in the city generation process to produce more realistic cities.

5.6.1.5 Question 103

Cities often have more than one nucleus of high buildings. These nuclei are separated from each other by zones of less tall buildings. As in the previous exercise, each core of buildings can be modelled by a Gaussian distribution. Assuming that the multiple cores are independent from each other, the buildings’ height can be modelled by overlapping Gaussian distributions, i.e., at each point, the buildings’ height is the maximum height of the Gaussian distributions of the multiple cores.

Use the previous approach to model a city with three cores of "skyscrapers", similar to the one presented in the following image:

5.6.1.6 Question 104

It is intended to create a set of \(n\) tangent spheres to a virtual sphere centred at \(p\), with a limit radius \(R_L\). The spheres are centred at a random distance of \(p\) which is a random value between an inner radius \(r_i\) and an outer radius \(r_e\), as presented in the following scheme:

Define a function called spheres-in-sphere that from the centre \(p\), the radii \(r_i\), \(r_e\) and \(r_l\) and also a \(n\) number, creates this set of spheres, able to produce images such as the following:

6 Structures

6.1 Introduction

Most functions defined in the previous sections aim at producing specific geometric shapes. In this section we will be addressing functions which aim at creating abstract geometric shapes, in the sense of being geometric shapes represented only by a cluster of positions in space. For example, a polygon line can be represented only by the sequence of positions through which it passes. That sequence of positions can, however, be used for various other purposes such as to create a sequence of spheres with centres in those positions or to define the trajectory of a tube passing through those positions. These examples show that the manipulation of these clusters of positions is independent from the subsequent purpose we wish to give them.

In order to manage clusters of positions as a whole it is necessary to group these positions in what is called data structures. These structures are particular arrangements of data that allow them to be treated as a whole. A phone book for example, can be perceived as a data structure which sets associations between names and phone numbers.

One of the most flexible data structures that we will be using exists in many programming languages and is called list.

6.2 Lists

A list is a sequence of elements. In Racket, we can build this sequence of elements using the list function. We can access each element of the list by using the list-ref function which, given a list and a position of an item of that list, tells us which element that is. Here is an example of the use of these operations:

> (define friends (list "Michael" "Peter" "Charles" "Mary"))
> (list-ref friends 0)

"Michael"

> (list-ref friends 3)

"Mary"

As we can see by the previous example, the list-ref function considers that the first element of a list occupies the zero position. The list function accepts any number of arguments, grouping them all in the same list. Besides these two, there are many other functions for manipulating lists. Let us first introduce the cons function, whose name is an abbreviation of the word construct, that allows us to create a larger list from an element and a list to which we wish to add it:

> (define new-friends (cons "John" friends))
> new-friends

'("John" "Michael" "Peter" "Charles" "Mary")

> friends

'("Michael" "Peter" "Charles" "Mary")

As we can see, the cons function adds an element to a list, producing a new list that incorporates the new element at the head of the list. We also note that the original list remains unchanged by the function cons. It is also important to note that when Racket returns a list as a result, it starts by writing an apostrophe () and then writes the elements of the list between parentheses, as if it was writing a combination. The apostrophe’s purpose is to indicate that the result is not a combination but a list.

The fact that Racket uses the same notation for both combinations and lists is not accidental, and due to us being able to have programs that create other programs.

This clarification is crucial when we use this notation because, without it, Racket would think that we are writing a combination to be evaluated, following the regular rules of evaluating combinations. Thus, the apostrophe informs Racket that we do not wish the usual evaluation procedure but rather to construct a list, as seen in following interaction:

> (+ 1 2)

3

> '(+ 1 2)

'(+ 1 2)

In order to "decompose" lists, Racket provides a pair of functions — car and cdr which can be seen as the inverse of the cons function. Whilst the cons function joins an element to a list, the car function indicates which element was joined and the cdr function indicates to which list it was joined.

Some Lisp dialects, including Racket, provide the synonyms first and rest for the car and cdr functions respectively.

Here is an example:

> (car new-friends)

"John"

> (cdr new-friends)

'("Michael" "Peter" "Charles" "Mary")

In other words, it is possible to say that the car function returns the first element of the list and the cdr function returns the rest of the list, i.e., the list after the first element.

Naturally, the functions car and cdr can be chained:

> (car (cdr new-friends))

"Michael"

> (cdr (cdr (cdr new-friends)))

'("Charles" "Mary")

> (car (cdr (cdr (cdr new-friends))))

"Charles"

Given that these kind of expressions are widely used in Racket, many combinations were created. The caddr function, for example, is defined so that (caddr exp) corresponds to (car (cdr (cdr exp))). The name of the function indicates which operations are to be performed. An "a" represents a car and a "d" represents a cdr.

From the previous examples, we can deduce that the list function does something very similar to a combination of the cons function:

> (cons 1 (list 2 3 4))

'(1 2 3 4)

> (cons 1 (cons 2 (list 3 4)))

'(1 2 3 4)

> (cons 1 (cons 2 (cons 3 (list 4))))

'(1 2 3 4)

> (cons 1 (cons 2 (cons 3 (cons 4 (list)))))

'(1 2 3 4)

There is but a single case when the list function cannot be decomposed into a composition of cons functions: the expression (list) produces a list with no elements, i.e., an empty list. Logicaly, Racket considers that the form '() represents a list with no elements. In fact, when we try to obtain the rest of a list with a single element we get '(), as when we try to build a list without elements:

> (cdr (list 1))

'()

> (list)

'()

As we will see, the functions that we will define will have to deal with empty lists, so we have to be able to recognize them. For this purpose we have the predicate null? which is true when applied to an empty list and false otherwise.

Some Lisp dialects, Racket included, provide a synonym for this function called empty?.

> (null? (list))

#t

> (null? friends)

#f

6.2.1 Pairs

We have seen that the cons function allows an element to be added to a list but actually it does something far more fundamental. This function accepts any two entities as arguments and produces a par with these two entities, i.e., a value representing a cluster of those two entities. It is common to say that this pair is a cons. The car and cdr functions are merely the selectors that return the first and second element of that pair. Here it is an example of the creation of a pair of numbers:

> (cons 1 2)

'(1 . 2)

> (car (cons 1 2))

1

> (cdr (cons 1 2))

2

Note that when Racket pretends to write the result of a pair creation, it begins by writing the apostrophe followed by an open parentheses, then, the first element of the pair, a point to separate, the second element of the pair, and finally a closing parenthesis. This notation is called "pair with point" or, as the original, dotted pair. However, when the second element of the pair is also a pair, Racket employs a simpler notation that omits the point and a pair of parentheses:

> (cons 1 (cons 2 3))

'(1 2 . 3)

In the previous example, if Racket was to write it using the pair with dot notation, it would have to write (1. (2. 3)).

Finally, when the second element of the pair is an empty list, Racket only writes the the first element between a pair of parenthesis.

> (cons 1 '())

'(1)

It is the combination of these two rules that allow us to visualize a sequence of pairs ending with an empty list as being a list:

> (cons 1 (cons 2 (cons 3 '())))

'(1 2 3)

6.2.2 Graphic Representation of Pairs

As stated above, when we write (cons \(\alpha\) \(\beta\)), we are creating a pair with the \(\alpha\) and \(\beta\) elements. This implies that memory will be reserved in our program to contain this pair, that we graphically represent with a box divided into two halves, the one on the left pointing to the first cons argument (the car) and the one on the right pointing to the second cons argument (the cdr). For example, the expression (cons 1 "two") can be represented in this graphical notation, called box and link, as follows:

Obviously, a cons can point to another cons. That is precisely what happens when we create lists. As we have seen, a list is no more than a cons in which the cdr is directly linked to another list (empty or not). For example, the list created by the expression (list 1 2 3) is equivalent to the list (cons 1 (cons 2 (cons 3 '()))). Its graphical representation is:

Here we see that from Racket’s point of view a list is nothing more than a particular arrangement of pairs wherein the second element of each pair is, either another pair, or an empty list.

Thus, the empty list '() is the starting point for building any list, as we can see in the following sequence of expressions:

> (cons 3 '())

'(3)

> (cons 2 (cons 3 '()))

'(2 3)

> (cons 1 (cons 2 (cons 3 '())))

'(1 2 3)

Note that in the above expressions the second argument of the cons function is either an empty list or a list containing some elements. In these cases, the result of invoking the cons function will always be a list.

6.3 Recursive Types

Obviously, whichever the list we come up with, it is always possible to create it only by using the cons function and the '() empty list. In fact, (list) is equivalent to '() and (list e1 e2 ... en) is equivalent to (cons e1 (list e2 ... en)).

Consequently, we can say that a list is always:

Note that there is a subtle detail in the list definition that we have just presented: it is recursive! To confirm this we just need to note that we are defining a list as a cons of one element to a list, i.e. we use the term we want to define in its own definition. As we know from any recursive definition, it is necessary to have a stopping condition which, in the case of lists, is the empty list.

When a data type is defined recursively it is usually called recursive type. Lists are, therefore, a recursive type. In a recursive type there must always be a primal element from which the remaining elements are created. That primal element is called primitive element. In case of lists, the primitive element is, obviously, the empty list.

As with other types of data, the various operations for manipulating lists can be classified as constructors, selectors and recognizers. It is easy to see that '() and cons are constructors, car and cdr are selectors, and finally null? is a recognizer. Using these operations it is possible to define several other operations that operate on lists.

6.4 Recursion in Lists

One of the interesting properties of recursive types is that all operations that process elements of recursive type tend to be implemented by recursive functions. For example, if we wish to define a function that tells us how many elements there are in a list, we have to think recursively:

To assure the correctness of our reasoning we must ensure that the assumptions applied to every recursive definitions can be verified, namely:

The verification of these assumptions is sufficient to make sure the function is correct.

Translated into Racket we have:

(define (number-of-elements list)
  (if (null? list)
    0
    (+ 1 (number-of-elements (cdr list)))))

An example:

> (number-of-elements (list 1 2 3 4))

4

> (number-of-elements (list))

0

It is important to note that the existence of sublists does not change the number of elements in a list. In fact, the elements of sublists are not considered elements of the list. For example:

> (list (list 1 2) (list 3 4 5 6))

'((1 2) (3 4 5 6))

> (number-of-elements (list (list 1 2) (list 3 4 5 6)))

2

In the previous example we can see that the list only contains two elements, despite each of them being lists with more elements. In fact, the number-of-elements function already exists in Racket with the name length.

Another useful operation is the one that allows us to obtain the \(n\)th element of a list. We saw that this function already exists and is called list-ref but let us define our own version. It is common to assume that for \(n=0\), one should obtain the first element of the list. To define this function we should once again think recursively:

Once again we notice that there is a decrease of the problem, that the reduction is equivalent to the original problem and the problem will approach the basic case. In Racket, we have:

(define (nth n list)
  (if (= n 0)
    (car list)
    (nth (- n 1) (cdr list))))
> (nth 2 '(first second third fourth fifth))

'third

Once again, the previous function already exists in Racket with the name list-ref.

It is often necessary to concatenate lists. To do so, we can imagine a concatenate function that, given two lists, returns a list containing the elements of the two lists, in the same order. For example:

> (concatenate (list 1 2 3) (list 4 5 6 7))

'(1 2 3 4 5 6 7)

As always, recursions helps solve the problem. Let us start by thinking how the problem can be simplified, i.e., in turning the original problem (concatenate (list 1 2 3) (list 4 5 6 7)) into a slightly simpler problem.

Since there are two lists as parameters, we can simplify the problem by making a reduction in one of them, i.e., we can consider a reduction on the first list:

(concatenate (list 2 3) (list 4 5 6 7))

of which the result is (2 3 4 5 6 7)., or, alternatively, a reduction on the second list:

(concatenate (list 1 2 3) (list 5 6 7))

of which the results in '(1 2 3 5 6 7).

Besides simplifying the problem, it is also necessary to ensure that we can find a way to make the problem’s simplification equivalent to the original problem. For the first case that is trivial, one must simply add the number 1 to the beginning of the list:

(cons 1 (concatenate (list 2 3) (list 4 5 6 7)))

For the second alternative that is much more difficult to do since we would have to insert the number 4 somewhere in the middle of the resulting list.

Thus, there should be no doubts that the function should take the form of:

(define (concatenate l1 l2)
  (if ???
    ???
    (cons (car l1)
          (concatenate (cdr l1) l2))))

We now need to define the basic case. In order to do so, we need only think that if we are reducing the first list in each recursive call, then there will come a moment when the first list is empty. What is then the concatenation of an empty list with any other list? Obviously it is the other list. So, we can completely define the function:

(define (concatenate l1 l2)
  (if (null? l1)
    l2
    (cons (car l1)
          (concatenate (cdr l1) l2))))

In fact, this function already exists in Racket with the name append. It has the additional advantage of receiving any number of arguments, for example:

> (append (list 0) (list 1 2 3) (list 4 5) (list 6 7 8 9))

'(0 1 2 3 4 5 6 7 8 9)

The inverse operation of concatenating lists is separating them. To keep it simple let us consider a function that obtains a sublist from a given list. This sublist includes all elements between two given indexes. Usually, the convention is that the sublist includes the element with the first index and excludes the second indexed element. For example:

> (sublist (list 0 1 2 3 4 5 6) 2 4)

'(2 3)

To define this function, we can recursively simplify the problem of obtaining the sublist (e0 e1 ... en) between the indexes \(i\) and \(j\) into the problem of obtaining the sublist e1 ... en between the indexes \(i-1\) and \(j-1\). When \(i\) is zero, then the problem is transformed into obtaining the sublist of (e0 e1 ldots {} en) between the indexes \(0\) and \(j\) to become the cons of e0 with the sublist of (e1 ldots {} en) between the indexes \(0\) and \(j-1\). Translating this algorithm to Racket, we have:

(define (sublist l begining end)
  (cond ((> begining 0)
         (sublist (cdr l) (- begining 1) (- end 1)))
        ((> end 0)
         (cons (car l)
               (sublist (cdr l) begining (- end 1))))
        (else
         (list))))
6.4.1 Exercises 30
6.4.1.1 Question 105

Define a delete-nth function that receives a number \(n\) and a list, and deletes the \(n\)th element of the list. Note that the the first element of the list corresponds to \(n=0\).

For example:

> (delete-nth 2 (list 0 1 2 3 4 5))

'(0 1 3 4 5)

6.4.1.2 Question 106

Write a change-nth function that receives a number \(n\), a list and an element and replaces the \(n\)th number of the list by the given element. Note that the first element of the list corresponds to \(n=0\).

For example:

> (change-nth 2 (list 0 1 2 3 4 5) 9)

'(0 1 9 3 4 5)

> (change-nth 2 (list "I am" "going" "to Coimbra") "to Lisbon")

'("I am" "going" "to Lisbon")

6.4.1.3 Question 107

Write a function that given a list of elements returns an element of that list chosen randomly

6.4.1.4 Question 108

Define the one-of-each function that, given a list of lists, creates a list, in order, with a random element of each list. For example:

> (one-of-each (list (list 0 1 2) (list 3 4) (list 5 6 7 8)))

'(0 3 7)

> (one-of-each (list (list 0 1 2) (list 3 4) (list 5 6 7 8)))

'(1 4 6)

> (one-of-each (list (list 0 1 2) (list 3 4) (list 5 6 7 8)))

'(0 4 8)

6.4.1.5 Question 109

Define the random-elements function that, given a number \(n\) and a list of elements returns \(n\) elements of that list picked randomly. For example:

> (random-elements 3 (list 0 1 2 3 4 5 6))

'(0 2 6)

6.4.1.6 Question 110

Redefine the random-elements function so that the result respects the order of the position that the elements had in the list from which they were chosen.

6.4.1.7 Question 111

As we have seen, the cons function adds a new first element to a list, the car returns the first element of a list and the cdr function returns the list without the first element.

Write the "reverse" of the cons, car and cdr functions, called snoc, rac and rdc that, instead of operating with the first element, operate with the last one. The snoc function receives an element and a list and adds the element to the end of the list. The rac returns the last element of the list. And the rdc returns a list containing all elements except the last one.

6.4.1.8 Question 112

Define a reverse function that receives a list and returns another list which has the same elements as the given list presented in reverse order.

6.5 Predicates on Lists

To test whether a particular entity is a list we can use the predicate list?. This predicate is true for any list (including the empty list) and false for everything else:

> (list? (list 1 2))

#t

> (list? 1)

#f

> (list? (list))

#t

> (list? (cons 1 2))

#f

In the last example, notice that the universal recognizer list? does not consider a simple pair of elements a list. For that, there is the predicate pair? that tests if the argument is a dotted pair:

> (pair? (cons 1 2))

#t

> (pair? (list 1 2))

#t

> (pair? (list))

#f

6.5.1 Exercises 31
6.5.1.1 Question 113

Given that lists are flexible data structures, very often we use lists containing other lists, which in turn may contain other lists, up to the depth level that we like. For example, consider the following list of positions:

> (list (list 1 2) (list 3 4) (list 5 6))

'((1 2) (3 4) (5 6))

or the following list of lists of positions:

> (list (list (list 1 2) (list 3 4) (list 5 6))
        (list (list 7 8) (list 9 0)))

'(((1 2) (3 4) (5 6)) ((7 8) (9 0)))

Write a function called flatten that receives a list (possibly containing sublists) as argument and returns another list with all the elements of the first list and in the same order, i.e.:

> (flatten '(1 2 (3 4 (5 6)) 7))

'(1 2 3 4 5 6 7)

6.5.1.2 Question 114

The concatenation of strings can be performed by the string-append function. Define the concatenate-strings function that, given a list of strings, returns a single string with the concatenation of all strings contained in the list. For example:

> (concatenate-strings (list "A" "view" "of" "the" "Sintra" "Mountains"))

"AviewoftheSintraMountains"

6.5.1.3 Question 115

The concatenate-strings function "glues" strings to each other without leaving any space between them. Define a new function called create-sentence that receives a list of strings and concatenates them leaving a space every other word. For example:

> (create-sentence (list "The" "view" "of" "the" "Sintra" "hills"))

"The view of the Sintra hills"

6.5.1.4 Question 116

Define the random-sentence function that receives a list of lists of words and returns a random sentence composed by words from each list. For example, the repeated evaluation of the following expression:

(random-sentence
   (list (list "AutoLisp" "Scheme" "Racket")
         (list "is a" "was always a" "remains a")
         (list "fantastic" "fabulous" "modern")
         (list "language.")))

can produce the following results:

"Racket is a modern language."
"Racket is a fantastic language."
"Scheme was always a modern language."
"AutoLisp remains a fantastic language."
"AutoLisp is a modern language."
"Scheme is a fantastic language."
"Scheme remains a great language."

Tip: Define the random-sentence function using the create-sentence and one-of-each functions.

6.5.1.5 Question 117

Consider creating a function for giving speeches. The basic idea giving a speech is to say the same thing in different ways and do not seem repetitive. Here is an example of the desired interaction:

> (random-speech)

"Esteemed colleagues. It is with great joy that I find myself reunited with you again in this place."

> (random-speech)

"Esteemed friends. It is with great pleasure that I see you again in this place."

> (random-speech)

"Dear friends. It is with huge joy that I find myself reunited with you again in this place."

> (random-speech)

"Dear friends. It is with great pleasure that I see you again here."

> (random-speech)

"Esteemed colleagues. It is with huge pleasure that I see you again in this place."

> (random-speech)

"Dear friends. It is with immense pleasure that I see you again in this room."

> (random-speech)

"Dear fellows. It is with huge pleasure that I find myself reunited with you again here."

> (random-speech)

"Esteemed fellows. It is with immense joy that I see you again in this place."

Study the presented interaction and define the random-speech function.

6.6 Enumerations

Let us now consider a function that will be useful in the future for enumerating. Given the limits \(a\) and \(b\) of an interval \([a, b]\) and an increment \(i\), the function should return a list of all numbers from \(a\), \(a+i\), \(a+2i\), \(a+3i\), \(a+4i\), ..., to \(a+ni>b\).

Once again, recursion helps: the enumeration of the numbers in the interval \([a, b]\) with an \(i\) increment is exactly the same as the number \(a\) followed by the enumeration of the numbers in the interval \([a + i, b]\). The simplest case of all is an enumeration in the interval \([a, b]\) wherein \(a>b\). In this case the result is simply the empty list.

This function’s definition is now trivial:

(define (enumerate a b i)
  (if (> a b)
    (list)
    (cons a
          (enumerate (+ a i) b i))))

As an example:

> (enumerate 1 5 1)

'(1 2 3 4 5)

> (enumerate 1 5 2)

'(1 3 5)

To make the function even more generic we can also cover the case where \(d\) is a negative increment. This is useful, for example, for a regressive counting: (enumerate 10 0 -1).

Note that the current definition of the enumerate function does not allow this because the use of a negative increment causes an infinite recursion. The problem lies in the fact that the stopping test is made with the function > that is only suitable for the case in which the increment is positive. In the case of a negative increment we should use <. So, to solve the problem we must first identify which is the correct operation for the comparison, something we can do with a simple test:

(define (enumerate a b i)
  (if ((if (> i 0)
         >
         <)
        a b)
    (list)
    (cons a
          (enumerate (+ a i) b i))))

Now we can have:

> (enumerate 1 5 1)

'(1 2 3 4 5)

> (enumerate 5 1 -1)

'(5 4 3 2 1)

> (enumerate 6 0 -2)

'(6 4 2 0)

6.6.1 Exercises 32
6.6.1.1 Question 118

Unfortunately, the enumerate function is unnecessarily inefficient because, despite the fact that the increment never changes along the recursive calls, we are systematically testing if it is positive. Obviously there is only the need to test it once in order to decide, once and for all, what test we should perform. Redefine the enumerate function so that no unnecessary tests are performed.

6.6.1.2 Question 119

The \(iota\) function (pronounced "iota") is an enumeration starting from \(0\) to an upper limit \(n\), excluding the \(n\) value, with the elements of the enumeration separated by a given increment, i.e.:

> (iota 10 1)

'(0 1 2 3 4 5 6 7 8 9)

> (iota 10 2)

'(0 2 4 6 8)

Define the iota function using the enumerate function.

6.6.1.3 Question 120

The enumerate function can have a bizarre behaviour when the increment is not an exact number. For example:

> (length (enumerate 0 1 1/100))

101

> (length (enumerate 0 1 0.01))

100

As we can see, when we use \(\frac{1}{100}\), i.e., an exact hundredth, the function produces a list with the correct length. However, when we use \(0.01\), an inexact hundredth, the function produces a list with one element missing. This is due to the fact that the number \(0.01\) cannot be represented exactly in binary notation, and so, as a sufficiently large amount of these numbers are added, a significant error is accumulated.

The Kahan algorithm allows us to minimize this problem. Do a search for this algorithm and use it to implement a new version of the enumerate function capable of avoiding accumulating errors when the increment is not an exact number.

6.6.1.4 Question 121

Define a function called member? that receives a number and a list and checks whether that number exists in the given list. Here are two examples of the use of said function:

> (member? 3 '(5 2 1 3 4 6))

#t

> (member? 7 '(5 2 1 3 4 6))

#f

6.6.1.5 Question 122

Define a function called eliminate1 that receives as arguments a number and a list and returns as a result another list in which the first occurrence of that number was eliminated. Here is an example:

> (eliminate1 3 '(1 2 3 4 3 2 1))

'(1 2 4 3 2 1)

6.6.1.6 Question 123

Define a function called eliminate that receives, as arguments a number and a list and returns as a result another list in which that number is completely eliminated. Here is an example:

> (eliminate 3 '(1 2 3 4 3 2 1))

'(1 2 4 2 1)

6.6.1.7 Question 124

Define a function called replace that receives two numbers and a list as arguments and returns another list with all the occurrences of the second argument replaced with the ones in the first one. Here is an example:

> (replace 0 1 '(1 2 1 3))

'(0 2 0 3)

> (replace 0 1 '(2 3 4 5))

'(2 3 4 5)

6.6.1.8 Question 125

Define a function called remove-duplicates that receives a list of numbers as argument and returns another list with all the elements of the first one but without repeated numbers, i.e.:

> (remove-duplicates '(1 2 3 3 2 4 5 4 1))

'(2 3 4 5 1)

6.6.1.9 Question 126

Define a function called occurrences that receives a number and a list as arguments and returns the number of occurrences of that number in the given list. Here is an example:

> (occurrences 4 '(1 2 3 3 2 4 5 4 1))

2

6.6.1.10 Question 127

Define a function called position that receives a number and a list as arguments and returns the position of the first occurrence of that number in the given list. Note that positions in a list start at zero. For example:

> (position 4 '(1 2 3 3 2 4 5 4 1))

5

6.7 Polygon

Lists are particularly useful to represent abstract geometrical entities, i.e., entities from which we are only interested in knowing certain properties such as, for example, their position. To do so we can use a list of positions, i.e., a list where the elements are the result of calling constructors of coordinates.

Let us imagine as as example that we want to represent a polygon. By definition, a polygon is a plane figure bounded by a closed path composed by a sequence of line segments. Each line segment is an edge (or side) of the polygon. Each point where two line segments meet is a vertex of the polygon.

Based on the polygon’s definition it is easy to infer that one of the simplest ways to represent a polygon will be through a sequence of positions that indicate in which order we should connect the vertices with an edge and admitting that the last element will be connected to and first one. It is precisely this sequence of arguments that we provide in the following example, where we use the pre-defined polygon function:

(polygon (xy -2 -1) (xy 0  2) (xy 2 -1))
(polygon (xy -2  1) (xy 0 -2) (xy 2  1))

Two overlapping polygons.

Suppose that, rather than explicitly indicate the vertices positions, we want a function to compute them. Naturally, that would require the function to compute a sequence of positions. It is precisely here that lists come to give an valuable help: they allow the representation of sequences and, to further simplify their use, many of the geometric functions, such as line and polygon, also accept lists as argument. In fact, this figure can also be obtained by the following expressions:

(polygon (list (xy -2 -1) (xy 0  2) (xy 2 -1)))
(polygon (list (xy -2  1) (xy 0 -2) (xy 2  1)))

Thus, a list with the positions of four vertices can represent a quadrilateral, an octagon can be represented by a list of eight vertices, a triacontakaihenagon can be represented by a list of thirty-one vertices, etc.. Now, we need only focus on creating these lists of positions.

6.7.1 Regular Stars

The polygon shown in this figure was generated "manually" by overlapping two triangles. A more interesting polygon is the famous pentagram, or five point star, that has been used with symbolic, magical or decorative purposes since the Babylon times.

The pentagram was a mathematical symbol for perfection to the Pythagoreans, was a symbol of spiritual mastery over the four natural elements to cultists, the symbol of the Five Holy Wounds of Christ to the Christians, was associated with the proportions of the human body, has been used by the Freemasonry and, when inverted, was associated with Satanism.

this figure demonstrates the use of the pentagram (as well as the octagram, the octagon and the hexagon) as a part of a structural and decorative window made of stone.

Variations of stars in the Amber Fort window located in the state of Jaipur, India. Photography by David Emmett Cooley

image

Apart from the extra-geometric connotations, the pentagram is, first of all, a polygon. For this reason it is drawable by the polygon function, as long as we can produce a list with the positions of the vertices. In order to do so, let us focus on this figure, where it is possible to see that the five pentagram vertices divide the circle into 5 parts, with arches measuring \(\frac{2\pi}{5}\) each. From the pentagram’s centre, its top vertex makes an angle of \(\frac{\pi}{2}\) with the abscissas axis. This vertex must be connected, not with the next vertex, but with the one immediately after it, i.e., after a rotation of two arcs or \(\frac{2\cdot 2\pi}{5}=\frac{4}{5}\pi\). We can now define the pentagram-vertices function that creates the list of vertices of the pentagram:

(define (pentagram-vertices p r)
  (list (+pol p r (+ (/ pi 2) (* 0 4/5 pi)))
        (+pol p r (+ (/ pi 2) (* 1 4/5 pi)))
        (+pol p r (+ (/ pi 2) (* 2 4/5 pi)))
        (+pol p r (+ (/ pi 2) (* 3 4/5 pi)))
        (+pol p r (+ (/ pi 2) (* 4 4/5 pi)))))
 
(polygon (pentagram-vertices (xy 0 0) 1))

Creation of a Pentagram.

It is obvious that the pentagram-vertices contains excessive repetition of code so it would be better if we found a more structured way of generating those vertices. To do so let us start by thinking about the generalizing the function.

A general case of a pentagram is the regular star, where one varies the number of vertices and the number of arcs between the connected vertices. A pentagram is a particular case of a regular star in which the number of vertices is five and the number of arcs between connected vertices is two. Mathematically, a regular star is represented by the Schläfli symbol

Ludwig Schläfli was a Swiss mathematician and geometer who made important contributions in the field of multidimensional geometry.

\(\{\frac{v}{a}\}\) where \(v\) is the number of vertices and \(a\) is the number of arcs between the connected vertices. In this notation a pentagram can be described as \(\{\frac{5}{2}\}\).

In order to draw regular stars let us think of a function that, given the centre of the star, the radius of the circumscribed circle, the number of vertices \(v\) (which we will call n-vertices) and the number of separation arcs \(a\) (which we will call n-arcs), calculates the size of the arc \(\Delta\phi\) between vertices. This arc is obviously \(\Delta\phi=a\frac{2\pi}{v}\). As in a pentagram, let us consider for the first vertex an initial angle of \(\phi=\frac{\pi}{2}\). From that first vertex one needs only successively increase \(\phi\) in increments of \(\Delta\phi\). The following functions implement this process:

(define (star-vertices p radius n-vertices n-arcs)
  (points-circle p
                 radius
                 pi/2
                 (* n-arcs (/ 2pi n-vertices))
                 n-vertices))
(define (points-circle p radius fi dfi n)
  (if (= n 0)
    (list)
    (cons (+pol p radius fi)
          (points-circle p
                         radius
                         (+ fi dfi)
                         dfi
                         (- n 1)))))

With the star-vertices function it is now trivial to generate the vertices of any regular star. This figure presents some regular stars generated by the following expressions:

(polygon (star-vertices (xy 0 0) 1 5 2))
(polygon (star-vertices (xy 2 0) 1 7 2))
(polygon (star-vertices (xy 4 0) 1 7 3))
(polygon (star-vertices (xy 6 0) 1 8 3))

Regular stars. From left to right we have a pentagram (\(\{\frac{5}{2}\}\)), two heptagrams (\(\{\frac{7}{2}\}\) and \(\{\frac{7}{3}\}\)), and one octagram (\(\{\frac{8}{3}\}\)).

This figure shows a sequence of regular stars \(\{\frac{20}{r}\}\) with \(r\) varying between \(1\) and \(9\).

(for/list ((n (in-range 1 9 1)))
  (polygon (star-vertices (xy (* n 2.5) 0) 1 20 n)))

Regular stars \(\{\frac{p}{r}\}\) with \(p=20\) and \(r\) varying between \(1\) (on the left) and \(9\) (on the right).

It is very important to understand that the construction of stars is separated into two distinct parts. On one hand, with the function star-vertices, we produce the coordinates of the stars’ vertices in the order by which they should be connected. On the other hand, with the polygon function, we use these coordinates to create a graphical representation of the star based on lines connecting its vertices. The transition of coordinates from one to the other is accomplished through a list that is "produced" on one side and "consumed" on the other.

The use of lists to separate different processes is fundamental and it will be by us repeatedly explored to simplify our programs.

6.7.2 Regular Polygons

A regular polygon is a polygon that has equal side lengths and equal angle amplitudes. This figure illustrates examples of regular polygons. Obviously a regular polygon is a particularly case of a regular star in which the number of arcs between vertices is one.

To create regular polygons, let us define a function called regular-polygon-vertices that generates a list of coordinates corresponding to the vertices of a regular polygon with \(n\) sides, inscribed in a circle of radius \(r\), centred at \(p\), of which the "first" vertex makes an angle \(\phi\) with the \(X\) axis. Being a regular polygon a particular case of a regular star, we only need to call the function that computes the vertices of a regular star but using, for the \(\Delta_\phi\) parameter, the value of the circumference division \(2\pi\) by the number of the sides \(n\):

(define (regular-polygon-vertices n p r fi)
  (points-circle p r fi (/ 2pi n) n))

Finally, we can encapsulate the generation of vertices with their use to create the corresponding polygon:

(define (regular-polygon n p r fi)
  (polygon
    (regular-polygon-vertices n p r fi)))

Regular polygons. From left to right we have a equilateral triangle, a square, a regular pentagon, a regular hexagon, a regular heptagon, a regular octagon a regular enneagon and a regular decagon.

Although we defined them here, the functions regular-polygon-vertices and regular-polygon are predefined in Rosetta.

6.7.3 Exercises 33
6.7.3.1 Question 128

Consider a polygon represented by a list of its vertices (p0 p1 ... pn) as presented in the diagram on the left:

We intend on dividing the polygon into two sub-polygons, as presented in the previous diagram on the right. Define the function divide-polygon that, given the list of vertices of the polygon and two indexes \(i\) and \(j\) that indicate where the division should be made (with \(i<j\)) calculates the lists of vertices of the resulting sub-polygons and returns them in a list. For example, in the presented figure we have: (divide-polygon (list p0 p1 p2 p3 p4) 1 4) produces a list of lists ((p0 p1 p4) (p1 p2 p3 p4))

6.7.3.2 Question 129

We can consider a generalization of the previous case based on the bisection of the polygon using an arbitrary line, as presented in the following diagram:

To simplify this process, consider that each end of the bisection line is located at a certain \(f_i\in [0,1]\) fraction of the distance that goes from the vertex \(P_i\) to the next vertex \(P_{i+1}\) (with \(P_{n+1}=P_0\)). For example, in the previous diagram, the vertices in question are \(P_1\) and \(P_4\) and the fractions respectively \(f_1=0.3\) and \(f_4=0.5\).

Define the function polygon-bisection that, given the list of vertexes of the polygon and the two indexes \(i\) and \(j\)immediately prior to the limits of the bisection line, and given the distance fractions (\(f_i\) and \(f_j\)), respectively between vertices \((P_i,P_{i+1})\) and \((P_j, P_{j+1})\), calculates the lists of vertexes of each of the resulting sub-polygons and returns them in a list (i.e., returns a list of a list of vertices).

6.7.3.3 Question 130

Define the function random-polygon-bisection that, given the polygon vertices, returns a list of lists of vertices of the sub-polygon corresponding to a random division of the polygon. The following image shows examples of random divisions of an octagon.

6.7.3.4 Question 131

Using the random-polygon-bisection function defined in the previous exercise, define the function random-polygon-division that randomly and recursively divides a polygon until a certain recursion level is achieved. The following image shows the random division of a decagon from \(0\) to \(10\) levels of recursion.

6.8 Polygonal Lines and Splines

We have seen that lists allow the storage of a variable number of elements and we have seen that is possible to use lists to separate programs that define the vertices of the geometric entities from the ones that use these vertices to represent them graphically.

In the case of polygon function discussed in the section Polygon, the lists of vertices are used to create polygons, i.e., flat figures limited by a closed path composed of a sequence of line segments. It so happens that we do not always want to create figures limited by closed paths, or that these paths are sequences of line segments, not even flat figures. To solve this problem we need to use functions capable of, given lists of positions, creating other types of geometric figures.

The simplest case is a polygonal line, not necessarily planar, that if open, can be created by the line function, and if closed, can be created by the closed-line function. In either case, these functions accept a variable number of arguments corresponding to the vertices of the polygonal line or a list of these vertices.

In case we do not want polygonal lines but "smooth" curves that pass through a sequence of positions, we can use the spline function to create open curves and the closed-spline function to create closed curves. As the line and closed-line functions, these also accept a variable number of arguments or a list of these arguments.

The difference between a polygonal line and a spline is visible in this figure, in which we compare a sequence of points linked with a polygonal line and the same sequence linked with a spline. The image was produced by evaluating the following expressions:

(define points
        (list (xy 0 2) (xy 1 4) (xy 2 0) (xy 3 3)
              (xy 4 0) (xy 5 4) (xy 6 0) (xy 2 7)
              (xy 8 1) (xy 9 4)))
(line points)

#<line 19127>

(spline points)

#<spline 19128>

Comparison between a polygonal line and a spline joining the same set of points.

Naturally, the "manual" specification of point coordinates is rather inconvenient, and it is preferable that those coordinates be automatically computed according to a mathematical definition of the desired curve. For example, imagine that we intend to draw a sine wave starting at a point \(P\). Unfortunately, in the list of geometrical figures provided by Rosetta (points, lines, line segments, rectangles, polygons, circles, arcs, etc), the sinusoid is not included.

In order to solve this problem we can create an approximation to a sinusoid curve. To do so we can calculate a sequence of points that belong to the sinusoid curve and join them using lines, or better yet, using curves that pass through those points. To calculate the set of values of the sine function in the interval \([x_0, x_1]\) we need only consider an increment ${\Delta_x} and, starting at point \(x_0\), and going from point \(x_i\) to point \(x_ {i +1}\) using \(x_{i+1}=x_i+\Delta_x\), we will be successively calculating the value of expression \(\sin(x_i)\) until \(x_i\) exceeds \(x_1\). To get a recursive definition for this problem we can think that when \(x_0>x_1\) the result will be an empty list of coordinates, otherwise we add the coordinate \((x_0,\sin(x_0))\) to the list of sine coordinates for the interval \([x_0+\Delta_x, x_1]\). This definition is directly translated to the following function:

(define (sine-points x0 x1 dx)
  (if (> x0 x1)
    (list)
    (cons (xy x0 (x0 sin))
          (sine-points (x0 + dx) dx x1))))

To have greater freedom in the positioning of the sinusoid curve in space, we can modify the previous function to incorporate a point \(p\) in relation to which the curve is positioned:

(define (sine-points p x0 x1 dx)
  (if (> x0 x1)
    (list)
    (cons (+y p (sin x0))
          (sine-points (+x p dx) (+ x0 dx) x1 dx))))

this figure shows the curves traced by the following expressions in which the points are joined through polygonal lines:

(line (sine-points (xy 0.0 1.0) 0.0 6.0 1.0))

#<line 19129>

(line (sine-points (xy 0.0 0.5) 0.0 6.5 0.5))

#<line 19130>

(line (sine-points (xy 0.0 0.0) 0.0 6.4 0.2))

#<line 19131>

Sinusoid curves drawn using plines with an increasing number of points.

Note that, in this figure, we gave a vertical offset to the curves to better understand the difference in accuracy between them. It is plainly evident that the more points used to calculate the curve, the more the polygonal line resembles the true curve. However, we must also take into account that increasing the number of points also forces Rosetta (and the CAD tool) to a greater computational effort.

For even better approximations, although at the cost of even greater computational effort, we can use splines, simply by changing the previous expressions to use the spline function instead of the line function. The result is presented in this figure.

Sinusoid curves drawn using splines with an increasing number of points.

6.8.1 Exercises 34
6.8.1.1 Question 132

Define the sinusoid-circular-points function, with parameters \(p\), \(r_i\), \(r_e\), \(c\) and \(n\) that computes \(n\) points of a closed curve in the shape of a sinusoid with \(c\) cycles that develops along a circular ring centred at the point \(p\) and enclosed by the inner radius \(r_i\) outer radius \(r_e\), as can be seen in the various examples presented in the following figure where, from left to right, the number of \(c\) cycles is 12, 6 and 3.

6.8.1.2 Question 133

Define the random-radius-circle-points function, with parameters \(p\), \(r_0\), \(r_1\) and \(n\) that computes \(n\) points of a closed random curve developing along a round circle centred at point \(p\) and enclosed by the inner radius \(r_0\) and outer radius \(r_1\), as can be seen in the various examples presented in the following figure where, from left to right, the number of points used gradually increased, thereby increasing the curve’s irregularity.

Tip: for computing the points, consider using polar coordinates to evenly distribute the points around a circle but with the distance to the centre varying randomly between \(r_0\) and \(r_1\). For example, consider that the leftmost curve in the previous figure was generated by the following expression:

(closed-spline
  (points-circle-radius-random
    (xy 0 0) 1 2 6))

6.9 Trusses

A truss is a structure composed by rigid bars that join together in nodes, forming triangular units. The triangle being the only polygon intrinsically stable, the use of triangles conveniently interconnected allows the trusses to be undeformable structures. Despite the simplicity of triangular elements, different arrangements of these elements allow for different types of trusses.

The Buckminster Fuller’s geodesic sphere. Photograph by Glen Fraser.

image

The use of trusses is known since ancient Greece, where they were used to support roofs. In the sixteenth century, in his Four Books of Architecture, Andrea Palladio illustrates bridges made of trusses. In the nineteenth century, with the extensive use of metal and the need to overcome increasing spans, various types of trusses were invented that were distinct in the different arrangements of vertical, horizontal and diagonal struts, and they are frequently denominated according with their inventors. Thus we have Pratt’s trusses, Howe’s trusses, Town’s trusses, Warren’s trusses, etc. In the last decades trusses began to be intensively used as an artistic element or for the construction of elaborate surfaces. The set of most famous examples includes Buckminster Fuller’s geodesic sphere for the Universal Exhibition of 1967, showed (reconstructed) in this figure and the banana shaped trusses by Nicolas Grimshaw for the Waterloo terminal, shown in this figure.

Trusses shaped in banana shape for the Waterloo terminal, by Nicolas Grimshaw. Photograph by Thomas Hayes.

image

Trusses show a set of properties that make them particularly interesting from an architectural point of view:

6.9.1 Drawing of Trusses

A fundamental step for drawing trusses is the design and construction of the fundamental triangular elements. Although it is common to only consider bi-dimensional trusses (also known as plane trusses), we will study the standard case of three-dimensional trusses composed by semi-octahedron. This type of truss is called space frame. Each semi-octahedron is called module.

This figure presents a diagram of a truss. Although the nodes in this truss are equally spaced along the parallel lines, it needs not be that way necessarily. This figure shows a different truss in which that does not happen.

Trusses composed by equal triangular elements.

Trusses in which the triangular elements are not equal amongst each other.

Therefore, for drawing a truss, let us consider, as working base, three arbitrary sequences of points in which each point defines one truss node. From these three sequences we can create the connections that need to be established between each pair of nodes. This figure shows the connection scheme of three sequences of points \(a_0,a_1, a_2\), \(b_0, b_1)\) and \(c_0, c_1, c_2)\). It should be noted that the top sequence established by the \(b_i\) points of the intermediate sequence always has one less element than the \(a_i0\) and \(c_i\) sequences.

Strut connection scheme of a truss in space frame.

For the truss construction we need to find a process that, from the lists of points \(a_i\), \(b_i\) and \(c_i\), not only creates the corresponding nodes for the various points, but also interconnects them in the correct way. Let us deal first with the nodes creation:

(define (truss-nodes ps)
  (if (null? ps)
    #t
    (begin
      (truss-node (car ps))
      (truss-nodes (cdr ps)))))

The truss-node function (note the use of the singular, by opposing the plural used in the truss-nodes function) receives the coordinates of a point and it is responsible for creating the three-dimensional model that represents the truss node centred in that point. One simple hypothesis will be for this function to create a sphere where the struts will be connected to, but, for now, let us leave the decision of what that model should be for latter and let us simply admit that the function truss-node will execute something appropriate to create the node. Thus, we can start idealizing the function that builds a complete truss from the following lists of points as, bs e cs:

(define (truss as bs cs)
  (truss-nodes as)
  (truss-nodes bs)
  (truss-nodes cs)
  ...)

After that, let us begin to establish the struts in between the nodes. From analysing this figure we know that we have one connection between each \(a_i\) and each \(c_i\), another between each \(a_i\) and \(b_i\), another between \(c_i\) and \(b_i\), another between \(b_i\) and \(a_{i+1}\), another between \(b_i\) and \(c_{i+1}\), another between \(a_i\) and \(a_{i+1}\), another between \(b_i\) and \(b_{i+1}\) and finally, another between \(c_i\) and \(c_{i+1}\). Admitting that the truss-strut function creates the three-dimensional model of that strut (for example, a cylinder, or a prismatic strut), we can start by defining a function denominated truss-struts (plural) that, given two lists of points ps and qs, creates connection struts along successive pairs of points. To create a strut, the function needs one element from ps and another from qs, which implies that the function should end as soon as one of these lists is empty. Thus the definition is:

(define (truss-struts ps qs)
  (if (or (null? ps) (null? qs))
    #t
    (begin
      (truss-strut (car ps) (car qs))
      (truss-struts (cdr ps) (cdr qs)))))

In order to interconnect each \(a_i\) node to the corresponding \(c_i\) node, we only need to evaluate (truss-struts as cs). The same can be said for interconnecting each node \(b_i\) to the corresponding node \(a_i\) and to interconnect each \(b_i\) to each \(c_i\). Thus, we have:

(define (truss as bs cs)
  (truss-nodes as)
  (truss-nodes bs)
  (truss-nodes cs)
  (truss-struts as cs)
  (truss-struts bs as)
  (truss-struts bs cs)
  ...)

To connect the \(b_i\) nodes to the \(a_{i+1}\) nodes we can simply subtract the first node from the list as and establish the connection as before. The same can be done to connect each \(b_i\) to each \(c_{i+1}\). Finally, to connect each \(a_i\) to each \(a_{i+1}\) we can use the same idea but applying it only to the list as. The same can be done to the list cs. The complete function will thus be:

(define (truss as bs cs)
  (truss-nodes as)
  (truss-nodes bs)
  (truss-nodes cs)
  (truss-struts as cs)
  (truss-struts bs as)
  (truss-struts bs cs)
  (truss-struts bs (cdr as))
  (truss-struts bs (cdr cs))
  (truss-struts (cdr as) as)
  (truss-struts (cdr cs) cs)
  (truss-struts (cdr bs) bs))

The previous functions creates trusses based on the "elemental" functions truss-node and truss-strut. Although their meaning is obvious, we have not yet defined these functions and there are several possibilities. For a first approach let us consider that every truss node will be formed by a sphere to which the struts will connect, with these struts defined by cylinders. The spheres and cylinders radii will be determined by a global variable, so that we can easily change its value. Thus, we have:

(define truss-node-radius 0.1)
(define (truss-node p)
  (sphere p truss-node-radius))
(define truss-strut-radius 0.03)
(define (truss-strut p0 p1)
  (cylinder p0 truss-strut-radius p1))

We can now create trusses with any form we want. This figure shows a truss drawn from the expression:

Truss build from arbitrary specified points.

(truss
 (list (xyz 0 -1 0) (xyz 1 -1.1 0) (xyz 2 -1.4 0) (xyz 3 -1.6 0)
       (xyz 4 -1.5 0) (xyz 5 -1.3 0) (xyz 6 -1.1 0) (xyz 7 -1 0))
 (list (xyz 0.5 0 0.5) (xyz 1.5 0 1) (xyz 2.5 0 1.5) (xyz 3.5 0 2)
       (xyz 4.5 0 1.5) (xyz 5.5 0 1.1) (xyz 6.5 0 0.8))
 (list (xyz 0 1 0) (xyz 1 1.1 0) (xyz 2 1.4 0) (xyz 3 1.6 0)
       (xyz 4 1.5 0) (xyz 5 1.3 0) (xyz 6 1.1 0) (xyz 7 1 0)))
6.9.1.1 Question 134

Define a straight-truss function capable of building any of the trusses shown in the following image.

To simplify, consider that the trusses develop along the \(X\) axis. The straight-truss function should receive the initial truss point, the height and width of the truss and the number of nodes of the lateral rows. With these values, the function should produce three lists of coordinates which will be given as arguments to the truss function. As an example, consider that the three trusses presented on the previous image were the result of the evaluation of the following expressions:

(line-truss (xyz 0  0 0) 1.0 1.0 20)
(line-truss (xyz 0  5 0) 2.0 1.0 20)
(line-truss (xyz 0 10 0) 1.0 2.0 10)

Suggestion: begin by defining a line-coordinates function that, given an initial pointy \(p\), a separation between points \(l\) and a \(n\) number of points, returns a list with the coordinates of the \(n\) points arranged along the \(X\) axis.

6.9.1.2 Question 135

The total cost of a truss depends heavily on the number of different lengths that the struts may have: the smaller that number is, greater economies of scale can be obtained and, consequently, the cheaper the truss will be. The ideal case is when all strut lengths are the same.

Given the following scheme, determine the height \(h\) of the truss in terms of the width \(l\) of the module so that all the struts have the same length.

Define also the truss-module function that builds a truss with all the struts having the same length, orientated along the \(X\) axis. The function should receive the truss initial point, its width and the number of nodes of the lateral rows.

6.9.1.3 Question 136

Consider the drawing of a plane truss, as shown in the following figure:

Define a plane-truss function that receives, as parameters, two lists of points corresponding to the points from \(a_0\) to \(a_n\) and from \(b_0\) to \(b_{n-1}\) and that creates the nodes in those points and the struts that unite them. Consider as pre-defined, the functions truss-nodes, that receives one list of points as argument, and the truss-struts function that receives two lists of points as arguments.

Test the function with the following expression:

(plane-truss
  (line-coordinates (xyz 0 0 0) 2.0 20)
  (line-coordinates (xyz 1 0 1) 2.0 19))
6.9.1.4 Question 137

Consider the special truss represented in the following figure:

Define a special-truss function that receives as parameters three lists of points corresponding to the points from \(a_0\) to \(a_n\), from \(b_0\) to \(b_{n-1}\) and from \(c_0\) to \(c_n\) and creates the nodes at those points and the struts that unite them. Consider as pre-defined the functions nodes-truss, that receives one list of points as argument, and struts-truss function that receives two lists of points as argument.

6.9.2 Creating Positions

As we saw in the previous section, it is possible to idealize process of creating a truss from the lists of the node positions. Naturally, these lists, can be specified manually but this approach will only be viable for very small trusses. However, a truss being a structure capable of covering very large spans, in general, the number truss nodes is too high for us to manually produce the list of positions. To solve this problem we need to think in automated processes to create these lists. These processes also have to take into account the desired geometry for the truss.

As an example, let us idealize the creation process for a truss in the shape of an arc, wherein the node sequences \(a_i\), \(b_i\) and \(c_i\) form circumference arcs. This figure shows one version of one such truss, defined by the circumference arcs of radius \(r_0\) and \(r_1\).

Front elevation of a truss in shape of a circular arc.

To make the truss uniform, the nodes are equally spaced along the arc. The angle \(\Delta_\psi\) corresponds to that spacing and its trivially calculated by dividing the arc angular amplitude by the number \(n\) of nodes required. Considering that the intermediate arc always has one less node than the lateral arcs, we need to divide the \(\Delta_\psi\) angle by the two extremities of the intermediate arc, in order to centre these arc’s nodes in between the nodes of the lateral arcs, as it is possible to see in this figure.

Since the arc is circular, the most simple way of calculating the node positions will be to use spherical coordinates \(\rho, \phi,\psi)\). This decision leads us to consider that the initial and final arcs angles should be measured relative to the \(Z\) axis, as visible in this figure. In order to make the production of node coordinates of each arc more flexible let us define a function that receives the centre \(P\) of the arc, the radius \(r\) of that arc, the angle \(\phi\), the initial \(\psi_0\) and final \(\psi_1\) angles and finally, the angle increment \(\Delta_\psi\). Thus, we have:

(define (arc-points p r fi psi0 psi1 dpsi)
  (if (> psi0 psi1)
    (list)
    (cons (+sph p r fi psi0)
          (arc-points p r fi (+ psi0 dpsi) psi1 dpsi))))

To build the arc truss we can now define a function that creates three of the previous arcs. For this, the function will receive the centre \(p\) of the central arc, the radius \(r_{ac}\) of the lateral arcs, the radius \(r_b\) of the central arc, the angle \(\phi\), the initial \(\psi_0\) and the final \(\psi_1\) angles and also, the separation \(e\) between the lateral arcs and the number \(n\) of nodes of the lateral arcs. The function will calculate the increment \(\Delta_\psi=\frac{\psi_1-\psi_0}{n}\) and then call the truss function with the appropriate parameters:

(define (arc-truss p rac rb fi psi0 psi1 e n)
  (define dpsi (/ (- psi1 psi0) n))
  (truss
    (arc-points (+pol p (/ e 2.0) (+ fi pi/2))
                 rac
                 fi
                 psi0 psi1
                 dpsi)
    (arc-points p
                 rb
                 fi
                 (+ psi0 (/ dpsi 2.0)) (- psi1 (/ dpsi 2.0))
                 dpsi)
    (arc-points (+pol p (/ e 2.0) (- fi pi/2))
                 rac
                 fi
                 psi0 psi1
                 dpsi)))

This figure shows the trusses built from the expressions:

(arc-truss (xyz 0 0 0) 10 9 0 -pi/2 pi/2 1.0 20)
(arc-truss (xyz 0 5 0)  8 9 0 -pi/3 pi/3 2.0 20)

Arc trusses created with different parameters.

6.9.2.1 Question 140

Consider the construction of vaults supported on trusses radially distributed, such as the one displayed in the following image:

This vault is composed by a determined number of circular arc trusses. The width \(e\) of each truss and the initial angle \(\psi_0\) with which each truss starts are such that the nodes at the top of the trusses are coincidental in pairs and are arranged along a circle of radius \(r\), as shown in the following scheme:

Define the truss-vault function that builds a vault of trusses from the vault centre point \(p\), the radius \(r_{ac}\) of the lateral arc of each truss, the radius \(r_b\) of the central arc of each truss, the radius \(r\) of the "end" of the trusses, the \(n\) number of nodes in each truss and, finally, the \(n_\phi\) number of trusses.

As an example, consider the following figure that was produced by the evaluation of the following expressions:

(truss-vault (xyz  0 0 0) 10 9 2.0 10 3)
(truss-vault (xyz 25 0 0) 10 9 2.0 10 6)
(truss-vault (xyz 50 0 0) 10 9 2.0 10 9)

6.9.2.2 Question 138

Consider the creation of a step ladder identical to the one shown on the following figure:

Note that the step ladder can be seen as a (very) simplified version of a truss composed only by two sequences of nodes. Define the step-ladder function that receives as parameters two lists of points corresponding to the centre of the nodes sequences and that creates the nodes at those points and the struts that join them. Consider as pre-defined the function truss-node, that receive one point as argument, and the function strut-truss, that receives two points as arguments.

6.9.2.3 Question 139

Define a function capable of representing the double genome helix, as shown in the following image:

The function should receive a position referring to the centre of the genome base, the radius of the genome helix, the angular difference between the nodes, the height difference between the nodes and, finally, the number of nodes in each helix. The genomes represented in the previous image were created by the following expressions:

(genome (xyz 0 0 0) 1.0 (/ pi 32) 0.5 20)
(genome (xyz 4 0 0) 1.0 (/ pi 16) 0.25 40)
(genome (xyz 8 0 0) 1.0 (/ pi 8) 0.125 80)
6.9.3 Spatial Trusses

We have seen how it is possible to define trusses from three lists each containing the nodes’ coordinates to which the struts are connected. Connecting these various trusses with each other it is possible to create an even bigger structure that is denominated as spatial truss. This figure shows an example where three spatial trusses are visible.

Spatial trusses in the AL Ain Stadium in the United Arabs Emirates. Photograph by Klaus Knebel.

image

In order to define an algorithm that generates spatial trusses it is important to take into account that although these type of trusses groups various simple trusses, these are interconnected in such a way that each truss shares a set of nodes and struts with the adjacent truss, as shown in this figure, where a a spatial truss scheme is presented. This way, if a spatial truss is composed by two simple interconnected trusses, the spatial truss will be generated, not by six lists of coordinates, but only by five lists of coordinates. In the general case, a spatial truss formed by \(n\) simple trusses will be defined by \(2n+1\) lists of points, i.e., by an uneven number of lists of points (minimum, by three lists).

Strut connection diagram of a spatial truss.

The definition of the function that creates a spatial truss follows the same logic for building a simple truss only now, instead of operating with only three lists, it operates with an uneven number of lists. Thus, from a list containing an uneven number of lists of coordinates, we will process these lists of coordinates two by two, knowing that the "third" list of coordinates \(c_{i,j}\) of truss \(i\) is also the "first" list of coordinates \(a_{i+1,j}\) of the following list \(i+1\).

Seeing as we process two lists at a time and start with an uneven number of lists, in the "final" case there will be only one list of coordinates left needed to "close" the creation of the truss.

There is, however an additional difficulty: for the plain truss to have transversal rigidity it is still necessary to interconnect the central nodes of the various trusses amongst each other. This transversal rigidity correspond to connecting each \(b_{i,j}\) node to the node \(b_{i+1,j}\). Thus, as we process the lists of coordinates, we will also establish the transversal rigidity between the corresponding lists. The entire process is implemented by the following function:

(define (spatial-truss curves)
  (let ((as (car curves))
        (bs (cadr curves))
        (cs (caddr curves)))
    (nodes-truss as)
    (nodes-truss bs)
    (struts-truss as cs)
    (struts-truss bs as)
    (struts-truss bs cs)
    (struts-truss bs (cdr as))
    (struts-truss bs (cdr cs))
    (struts-truss (cdr as) as)
    (struts-truss (cdr bs) bs)
    (if (null? (cdddr curves))
        (begin
          (nodes-truss cs)
          (struts-truss (cdr cs) cs))
        (begin
          (struts-truss bs (cadddr curves))
          (spatial-truss (cddr curves))))))
6.9.3.1 Question 141

In reality, a simple truss is a particular case of a spatial truss. Redefine the truss function in a way that it uses the spatial-truss function.

Now that we know how to build spatial trusses from a list of lists of coordinates, we can think of mechanisms to generate this list of lists. A simple example is a horizontal spatial truss, as the one presented in the this figure.

An horizontal spatial truss composed of eight simple trusses with ten pyramids each.

To generate the coordinates of the nodes we can define a function that, based on the number of desired and on the width of the pyramid base, generates the nodes along one of the dimensions, for example, the \(X\) dimension:

(define (line-coordinates p l n)
  (if (= n 0)
    (list)
    (cons p
          (line-coordinates (+x p l) l (- n 1)))))

Next, we only have to define another function that iterates the previous one along the \(Y\) dimension, in order to generate a line of nodes \(a_{i}\), followed by another line \(b_{i}\) displaced to the centre of the pyramid and to its height, followed by the remaining lines, until the end, when we have to produce another line \(a_{i}\). It is still necessary to take into account that the lines \(b_{i}\) have one less node then the lines \(a_{i}\). Based on these considerations, we can write:

(define (horizontal-pyramid-coordinates p h l m n)
  (if (= m 0)
    (list (line-coordinates p l n))
    (cons
     (line-coordinates p l n)
     (cons
       (line-coordinates
         (+xyz p (/ l 2) (/ l 2) h) l (- n 1))
       (horizontal-pyramid-coordinates
         (+y p l) h l (- m 1) n)))))

We can now combine the list of lists of coordinates created by the previous function with the one that creates a spatial truss. As an example, the following expression creates the truss shown in this figure:

(spatial-truss
  (horizontal-pyramid-coordinates (xyz 0 0 0) 1 1 8 10))
6.9.4 Exercises 35
6.9.4.1 Question 142

Consider the creation of a random spatial truss. This truss is characterized by its nodes’ coordinates being positioned at a random distance from the correspondent nodes’ coordinates of a horizontal spatial truss, as it is exemplified in the following figure where, to facilitate the visualization, the struts that join the \(a_i\), \(b_i\) and \(c_i\) nodes where highlighted in the scheme shown in this figure.

Define the function random-truss-coordinates that, in addition to the horizontal-pyramid-coordinates function parameters, also receives the maximum distance \(r\) that each node from the random truss can be placed relatively to the correspondent node of the horizontal truss. As an example, consider that the truss presented in the previous figure was created by the evaluation of the following expression:

(spatial-truss
  (random-truss-coordinates (xyz 0 0 0) 1 1 8 10 0.2))
6.9.4.2 Question 143

Consider the creation of arched spatial truss, like the one presented in the following image (perspective view). Define the arched-spatial-truss function that, besides the arc-truss function parameters, also has an additional parameter that indicates the number of simple trusses that constitute the spatial truss. As an example, consider that the truss shown in the following image was created by the expression:

(arched-spatial-truss (xyz 0 0 0) 10 9 1.0 20 0 -pi/3 pi/3 10)

6.9.4.3 Question 144

Consider the truss shown in the following image:

This truss is similar to the arched spatial truss but with a nuance: the exterior arc rac and the interior arc rb vary along the arc axis. This variation corresponds to a sinusoid of amplitude \(\Delta_r\) varying from an initial value \(\alpha_0\) to a final value \(\alpha_1\), with increments of \(\Delta_\alpha\).

Define the wave-truss function that creates these types of trusses, with the same parameters as the arched-spatial-truss function and also with the parameters \(\alpha_0\), \(\alpha_1\), \(\Delta_\alpha\) and \(\Delta_r\). As an example, consider that the previous figure was created by the following expression:

(wave-truss (xyz 0 0 0) 10 9 1.0 20 0 -pi/3 pi/3 0 4pi (/ pi 8) 1)

7 Constructive Solid Geometry

7.1 Introduction

So far we have only dealt with curves and simple solids. In this section we will discuss more complex shapes created from the union, intersection and subtraction of simpler shapes. As we will see, these operations are often used in architecture.

This figure illustrates the temple of Portunus (also known, erroneously, as temple of Fortuna Virilis), a construction from the first century before Christ characterized for using columns, not as an eminently structural element, but as a decorative element. To do so, the architects considered a union between a structural wall and a set of columns, so that the columns would be embedded in the wall. This same approach is visible in other monuments such as, for example, the Coliseum of Rome.

Temple of Portunus in Rome, Italy. Photograph by Rickard Lamré.

image

In the case of the building visible in this figure, authored by Rojkind Arquitectos, the architect created an innovative shape through the subtraction of spheres to parallelepiped shapes.

Nestlé Application Group in Querétaro, Mexico. Photograph by Paúl Rivera - archphoto.

image

This figure demonstrates a third example. It is the case of the Sagrada Familia Cathedral, by Catalan architect Antoni Gaudí. As we will see in section (part "sec:gaudiColumns"), some of the columns idealized by Gaudí for this masterpiece are a result of the intersection of twisted prisms.

Columns of the Sagrada Família Cathedral in Barcelona, Spain. Photograph by Salvador Busquets Artigas.

image

7.2 Constructive Geometry

Constructive solid geometry is one of the most common techniques used for modelling solids. This approach is based on the combination of simple solids, such as parallelepipeds, spheres, pyramids, cylinders, torus, etc. Each of these solids might be seen as a set of points in space and their combination is achieved using set operations such as union, intersection and subtraction of those sets of points. To simplify, let us refer to the set of points in space as region.

Let us start by considering the operation of union. Given the regions \(R_0\) and \(R_1\), their union \(R_0\cup R_1\) is the group of points which either belongs to \(R_0\) or to \(R_1\). This operation is implemented in Rosetta by the union function. This figure shows, on the left side, the union between a cube and a sphere, produced by the following expression:

(let ((cube (box (xyz 0 0 0) (xyz 1 1 1)))
      (sphere (sphere (xyz 0 1 1) 0.5)))
  (union cube sphere))

Another operation is the intersection of regions \(R_0\cap R_1\), which produces the group of points that belong simultaneously to the sets \(R_0\) e \(R_1\) and that, obtained in Rosetta with the intersection function. The second image of this figure shows the intersection between a cube and a sphere and was produced by the expression:

(let ((cube (box (xyz 2 0 0) (xyz 3 1 1)))
      (sphere (sphere (xyz 2 1 1) 0.5)))
  (intersection cube sphere))

Finally, there is also the operation of subtraction of regions \(R_0\setminus R_1\) which corresponds to the group of points that belongs to \(R_0\) but do not belong to \(R_1\). Contrary the previous ones, this operation is not commutative. Thus, subtracting a sphere to a cube is different to subtracting a cube to a sphere. This difference is visible in the two images on the right side of this figure which were produced by the expressions:

(let ((cube (box (xyz 4 0 0) (xyz 5 1 1)))
      (sphere (sphere (xyz 4 1 1) 0.5)))
  (subtraction cube sphere))

and

(let ((cube (box (xyz 6 0 0) (xyz 7 1 1)))
      (shpere (sphere (xyz 6 1 1) 0.5)))
  (subtraction sphere cube))

Volumes Combination. The image to the left represents the union of a cube with a sphere, the image in the centre represents the intersection of the cube with the sphere and the one on the right represents the subtraction of the cube by the sphere.

Like other previously discussed functions, such as line and spline, the functions union, intersection, and subtraction receive any number of arguments or, alternatively, a list with all the arguments. As an example of the use of these operations, let us consider three cylinders placed along the \(X\), \(Y\) and \(Z\) axis. The union of these cylinders can be seen on the left side of this figure and was generated by the expression:

(union (cylinder (xyz -1 0 0) 1 (xyz 1 0 0))
       (cylinder (xyz 0 -1 0) 1 (xyz 0 1 0))
       (cylinder (xyz 0 0 -1) 1 (xyz 0 0 1)))

This object has the characteristic of, when viewed from the top, lateral elevation and frontal elevation, being reduced to a square. On the right side of the same figure we have an even more interesting solid, generated by the intersection of the same three cylinders, producing an object that, obviously, is not a sphere but, as a sphere, projects a circle in a top view, frontal and lateral elevation.

The union and intersection of three cylinders orthogonally arranged.

To better understand the difference between the object built through the intersection of the cylinders and a real sphere, we can subtract the sphere to the object. To be able to "peek" inside of the object, we will subtract a sphere with a (very) slightly bigger radius than the cylinders:

(subtraction
  (intersection
    (cylinder (xyz -1 0 0) 1 (xyz 1 0 0))
    (cylinder (xyz 0 -1 0) 1 (xyz 0 1 0))
    (cylinder (xyz 0 0 -1) 1 (xyz 0 0 1)))
  (sphere (xyz 0 0 0) 1.01))

The result is presented in this figure.

Subtraction of a sphere to the intersection of three cylinders orthogonally arranged. The sphere has a radius \(1\%\) higher to that of the cylinders so we can view the inside.

7.2.1 Exercises 36
7.2.1.1 Question 145

We wish to model a stone basin identical to the one presented in the following image:

The parameters relevant for the basin are represented in the frontal elevation, plan and lateral elevation presented in the following image:

Define a function called basin that builds a basin identical to the one on the previous image.

7.2.1.2 Question 146

We wish to model a stone bathtub identical to the one presented in the following image:

The parameters relevant for the bathtub are represented in the frontal elevation, plan and lateral elevation presented in the following image:

Define a function named bathtub which builds a bathtub identical to the one on the previous image.

7.3 Surfaces

Until now, we have used Rosetta to create curves, which we normally visualize in two dimensions, for example in the\(XY\) plane, or to create solids, which we see in three dimensions. Now, we will see that Rosetta also allows the creation of surfaces.

There are many ways for Rosetta to create a surface. Many of the functions which produce closed curves have a version with the same parameters but with the difference of producing a surface limited by those closed curves. Therefore, we have the functions surface-circle, surface-rectangle, surface-polygon, and surface-regular-polygon, which receive exactly the same arguments as, respectively, the functions circle, rectangle, polygon, and regular-polygon, but which produce surfaces instead of curves. Besides these ones, there is also the surface-arc function which produces a surface limited by an arch and by the radii between both extremities and the centre of the arch. Finally, there is still the surface function which receives a curve or a list of curves and produces a surface limited by that curve or curves. In fact, we have:

(surface-circle ldots)\(\equiv\)(surface (circle ldots))

(surface-rectangle ldots)\(\equiv\)(surface (rectangle ldots))

(surface-polygon ldots)\(\equiv\)(surface (polygon ldots))

and, in the same way, the equivalences for the remaining functions are established.

As with solids, surfaces can also be combined with the operation of union, intersection and subtraction to create more complex surfaces. For example, let us consider the following union between a triangular surface and a circle:

(union (surface-polygon (xy 0 0) (xy 2 0) (xy 1 1))
       (surface-circle (xy 1 1) 0.5))

with the result displayed on the upper left corner of this figure.

Combination operations of surfaces applied to a triangular surface and a circular surface. On the upper left corner we have the union and on the right the intersection. On the lower left corner we have the subtraction of the first by the second and on the right the second by the first.

If, instead of the union, we had chosen the intersection, then the result would be the one represented in the upper right corner of this figure. The subtraction of the circle from the triangle is represented in the lower left corner of this figure and, since the subtraction is not a commutative operation, we can also have the reverse subtraction, shown in the lower right corner of this figure.

7.3.1 Trefoils, Quatrefoils and Other Foils

The trefoil is an architectural element which had a widespread use during the Gothic period. It is an ornament made up of three tangent circles arranged around a centre, usually placed on the top of Gothic windows but can be found in several other places. Besides the trefoil, Gothic architecture also explores the Quatrefoil, the Pentafoil and other "foils". In this figure we present several examples of this element, coupled with some of its derivations.

Trefoils, quatrefoils, pentafoils and other "foils" in a window of Saint Peter’s Cathedral in Exeter, England. Photograph by Chris Last.

image

In this section we will use the operations which allow the creations and combination of surfaces to build trefoils, quatrefoils and more generically, \(n\)-foils. For now let us just consider the trefoil.

To determine the parameters of a trefoil let us focus on this figure where we present a "laid down" trefoil. In that figure we can identify \(R_e\) as the external radius of the trefoil, \(R_f\) as the radius of each "leaf" of the trefoil, \(\rho\) the distance from the centre of each leaf to the centre of the trefoil and \(R_i\) the radius of the inner circle of the trefoil. Since the trefoil divides the circumference into three equal parts, the angle \(\alpha\) will necessarily be half of a third of the circumference, in other words, \(\alpha=\frac{\pi}{3}\). In the case of a quatrefoil we will have, obviously, \(\alpha=\frac{\pi}{4}\) and in the general case of a \(n\)-foil we will have \(\alpha=\frac{\pi}{n}\).

Parameters of a trefoil.

Applying the trigonometric relations we can relate the trefoil parameters with each other:

\[r_f=\rho\sin\alpha\] \[r_i=\rho\cos\alpha\] \[\rho+r_f=r_e\]

If we assume that the fundamental parameter is the trefoil’s exterior radius, \(R_e\), then we can deduce that:

\[\rho=\frac{r_e}{1+\sin\frac{\pi}{n}}\] \[r_f=\frac{r_e}{1+\frac{1}{\sin\frac{\pi}{n}}}\] \[r_i=r_e\frac{\cos\frac{\pi}{n}}{1+\sin\frac{\pi}{n}}\]

From these equations, we can decompose the creation process of a \(n\)-foil as the union of a sequence of circles of radius \(R_f\) arranged circularly with a last central circle of radius \(R_i\). Transcribing to Racket, we have:

(define (n-folio p re n)
  (union
    (leafs-folio p re n)
    (circle-interior-folio p re n)))

The function circle-interior-folio is trivially defined as:

(define (circle-interior-folio p re n)
  (surface-circle
    p
    (* re
       (/ (cos (/ pi n))
          (+ 1 (sin (/ pi n)))))))

For the function leaves-folio, responsible for creating the circularly arranged leaves, we will consider applying polar coordinates. Each leaf (of radius \(R_i\)) will be placed in a polar coordinate determined by the radius \(\rho\) and by an angle \(\phi\) that we will recursively increment with \(\Delta_\phi=\frac{2\pi}{n}\). For that, we will define a new function to implement this process:

(define (union-circles p ro fi d-fi rf n)
  (if (= n 0)
    ???
    (union
      (surface-circle (+pol p ro fi) rf)
      (union-circles p ro (+ fi d-fi) d-fi rf (- n 1)))))

The problem now is what is what should the function return when n is zero. To better understand the problem, let us imagine we name the circles n as c1, c2, ..., cn. Given that, during the recursion, the function will leave the unions pending, when we reach the stopping condition we have:

(union c1
       (union c2
 
                 ...
 
                       (union cn
                              ???)))

It is now clear that ??? has to be something that can be used as an argument for an union and that, besides that, has to not affect the pending unions. This implies that ??? has to be the neutral element of the union \(\varnothing\). It is precisely for that purpose that Rosetta provides the empty-shape function. When invoked, the empty-shape function produces an empty region, i.e., a set of empty of points in space, literally representing an empty shape that, consequently, is a neutral element in the union of shapes.

Using this function, we can write the full algorithm:

(define (union-circles p ro fi d-fi rf n)
  (if (= n 0)
    (empty-shape)
    (union
      (surface-circle (+pol p ro fi) rf)
      (union-circles p ro (+ fi d-fi) d-fi rf (- n 1)))))

We are now in the condition to define the leaves-folio function which invokes the previous one with the pre-calculated values of \(\rho\), \(\Delta_\phi\) e \(R_f\). To simplify, let us consider an initial angle \(\phi\) of zero. Hence, we have:

(define (leaves-folio p re n)
  (union-circles
    p
    (/ re (+ 1 (sin (/ pi n))))
    0
    (/ 2pi n)
    (/ re (+ 1 (/ 1 (sin (/ pi n)))))
    n))

Based on these functions, we can create a trefoil next to a quatrefoil, like we present in this figure, simply by evaluating the following expressions:

(n-folio (xy 0 0) 1 3)

n-folio (xy 2.5 0) 1 4) }

A trefoil and a quatrefoil.

Naturally, we can use the function to generate other "foils." this figure shows a pentafoil, hexafoil, a heptafoil and an octafoil.

Foils with increasing number of leaves. From top to bottom and from left to right we have a pentafoil, a hexafoil, a heptafoil, an octofoil, an eneafoil and a decafoil.

Having a generic mechanism for constructing \(n\)-foils, it is tempting to explore its limits, particularly when we reduce the number of leaves. What will a two leaves foil be?

Unfortunately, when we attempt at creating one of these "bifoils," we find an error caused by the circle-interior-folio function. The error occurs because, in truth, as the number of foils decreases, the radius of the interior circle diminishes until it becomes simply zero. In fact,for \(n=2\), the radius of the interior circle is \[r_i=r_e\frac{\cos\frac{\pi}{2}}{1+\sin\frac{\pi}{2}}\] that is, \[r_i=r_e\frac{0}{2}=0\]

It is not hard to fix the problem by inserting a test in the function n-folio that prevents the union of the leaves with the interior circle when the latter has zero radius. However, that is not the best option because, from a mathematical point of view, the algorithm for constructing foils we idealized is still perfectly correct: it unites an interior circle to a set of leaves. It so happens that when the interior circle has zero radius it represents an empty set of points, in other words, an empty shape, and the union of an empty shape with the circles that correspond to the leaves does not alter the result.

A "bifoil".

Based on the possibility of creating empty shapes, we can now redefine the function circle-interior-folio in a way that, when the number of leaves is two, the function returns an empty shape:

(define (circle-interior-folio p re n)
  (if (= n 2)
    (empty-shape)
    (surface-circle p
                    (* re
                       (/ (cos (/ pi n))
                          (+ 1 (sin (/ pi n))))))))

Applying this redefinition of the function, it is now possible to evaluate the expression (n-folio (xy 0 0) 1 2) without generating any error and producing the correct bifoil, visible in this figure.

With the possibilities of creating bifoils, trefoils, quatrefoils and \(n\)-fólios, we can also legitimately think in the concepts of unifoil and zerofoil and try to imagine their form. Unfortunately, here we bump into mathematical limitations more difficult to overcome: when the number of foils is one, the radius \(R_i\) of the interior circle becomes negative, losing all geometric sense; when the number of foils is zero, the situation is equally absurd, as the angle \(\alpha\) becomes an impossibility due to being divided by zero. For these reasons, the bifoil is the lower limit of the \(n\)-foils.

7.4 Algebra of Shapes

In the previous section we saw the necessity of introducing the concept of empty shape. This necessity becomes more evident when we compare the operations for combining shapes with algebraic operations, such as the sum and the product. To begin with, let us consider a function which adds a list of numbers:

(define (sum numbers)
  (if (null? numbers)
    0
    (+ (car numbers)
       (sum (cdr numbers)))))

As we can see in the previous function, the sum of an empty list of numbers is zero. This makes sense since if there are no numbers to add, the total is necessarily zero. On the other hand, when we think that during the recursion some sums remain pending, when it reaches the stopping condition, it is required it returns a value that does not affect the pending sums and, once again, it makes sense that this value has to be zero because that is the neutral element of the sum.

Likewise, if we think of a function that calculates the union of a list of regions, we should write:

(define (unions regions)
  (if (null? regions)
    (empty-shape)
    (union (car regions)
           (unions (cdr regions)))))

because (empty-shape) is the neutral element of the union of regions.

let us now consider a function that multiplies a list of numbers, where we left empty the decision of which the value of the function will be in the basic case:

(define (product numbers)
  (if (null? numbers)
    ???
    (* (car numbers)
       (product (cdr numbers)))))

Though most people might be tempted to use the number zero as the basic case value, that would be incorrect as that zero, being the absorbing element of the product, would be propagated throughout the sequence of products that would be pending until the basic case, producing a final result of zero. Thus, it becomes clear that the correct value for the basic scenario has to be 1, i.e., the neutral element of the product:

(define (product numbers)
  (if (null? numbers)
    1
    (* (car numbers)
       (product (cdr numbers)))))

This analysis is crucial so that we can now correctly define a function to calculate the intersection of a list of regions. Although it is trivial to write a first draft

(define (intersections regions)
  (if (null? regions)
    ???
    (intersection (car regions)
                  (intersections (cdr regions)))))

it is as evident knowing what to place instead of ???. What we know is that that value has to be the neutral element of the combination operation which, in this case, is the intersection. Fortunately, it is not hard to conclude that that neutral element is, necessarily, the universal shape \(U\), i.e., the shape that includes all the points of space. That shape is produced by the function universal-shape. This way, we can now define:

(define (intersections regions)
  (if (null? regions)
    (universal-shape)
    (intersection (car regions)
                  (intersections (cdr regions)))))

It is important we understand that the need for the empty-shape and universal-shape functions results from us having to assure the correct mathematical behaviour of the union, intersection and subtraction operations of regions. That mathematical behaviour is dictated by the following functions of the algebra of regions:

\[R \cup \varnothing = \varnothing \cup R = R\] \[R \cup U = U \cup R = U\] \[R \cup R = R\] \[R \cap \varnothing = \varnothing \cap R = \varnothing\] \[R \cap U = U \cap R = R\] \[R \cap R = R\] \[R \setminus \varnothing = R\] \[\varnothing \setminus R = \varnothing\] \[R \setminus R = \varnothing\]

7.4.1 Exercises 37
7.4.1.1 Question 147

The algebra of regions we elaborated is incomplete for not including the complement operation of a region. The complement \(R^C\) of a region \(R\) is defined as the subtraction to the universal region \(U\) of the region \(R\):

\[R^C=U\setminus R\]

The complement operation allows the representation of the concept of hole. A hole with the shape of the region \(R\) is obtained simply through \(R^C\). A hole has no obvious geometric representation, as it is difficult to imagine a hole without knowing which region it is a hole to, but, from the mathematical point of view, the concept will make sense if the algebraic operations on the regions know how to interpret it. For example, given the hole \(R_1^C\), we can apply it to a region \(R_0\) through the intersection of the two regions \(R_0\cap R_1^C\). As CAD tools do not know how to handle the complement of regions, the operation will have to be translated in terms of other operations already known. In this case, the subtraction is an obvious choice, as \(R_0\cap R_1^C=R_0\setminus R_1\).

To complete the algebra of holes it is necessary we define the result of the union, intersection and subtraction operations when applied to holes. Define mathematically those operations, as well as the combination between "normal" regions and holes.

7.4.1.2 Question 148

Since Rosetta does not implement the concept of complement, define a constructor for the complement of regions. The constructor should receive the region from which the complement is intended and should return an object that symbolically represents the complement of the region. Do not forget that the complement of a region’s complement is the region itself.

Also define a recognizer of complements that can receive any type of object and will only return true for those that represent complements of regions.

7.4.1.3 Question 149

Define the union, intersection and subtraction operations of regions so as to deal with the complement of regions.

7.4.1.4 Question 150

Consider the construction of a region composed by a recursive union of regular polygons successively smaller and centred at the vertices of the polygon immediately bigger as seen in the three examples presented in the following figure:

As it happened with the vertices-polygon-regular function defined in Section~\ref{sec:polygonsRegular}, each of these polygons is characterized for being inscribed in a circumference with radius \(R\) centred at a point \(p\), for having a "first" vertex that makes an angle \(\phi\) with the axis \(X\) and for having a certain number of sides \(n\). Furthermore, the construction of regular polygons is done in a way that each vertex is the centre of a new identical polygon, but inscribed in a circle whose radius is a fraction (given by \(\alpha_r\)) of the radius \(R\), with this process being repeated a certain number of levels. For example, in the previous figure, the image to the left, was created with \(p=(0,0)\), \(R=1\), \(\phi=0\), \(\alpha_r=0.3\), \(n=4\) and with number of levels equal to \(2\). As a whole, the images were produced by the evaluation of the following expressions:

(recursive-polygons (xy 0 0) 1 0 0.3 4 2)
(recursive-polygons (xy 3 0) 1 pi/3 0.3 3 3)
(recursive-polygons (xy 6 0) 1 0 0.3 5 4)

Petronas Towers, located at Kuala Lumpur, in Malaysia. photograph by Mel Starrs.

image

7.4.1.5 Question 151

The Petronas towers were designed by Argentine architect César Pelli and were considered the tallest buildings in the world from 1998 to 2004. This figure shows a perspective from the top of one of the towers.

The towers’ section, strongly inspired by islamic motifs, is composed by two intersecting squares rotated by a quarter of a circle and to which circles were added at the intersection points, as it can be seen in the constructive diagram.

Note that the circles are tangent to the imaginary edges that connect the vertices.

Define a Racket function named petronas-section that receives the centre of the section and the width \(l\) and produces the section of the Petronas tower. Suggestion: use the surface-regular-polygon and union-circles functions to generate the relevant regions.

7.4.1.6 Question 152

Define the function petronas-tower that, from the base centre, builds a succession of sections of the Petronas tower in order to reproduce the real geometry of Petronas tower, as illustrated in the following perspective where the function was invoked twice in different positions:

7.4.1.7 Question 153

Consider the following image:

The image represents a shelter with a cuboid shape built with round tubes cut so that the interior space has the shape of a quarter of a sphere. Note that the round tubes have a thickness which is \(10\%\) of their radius. Also note the relation between the radius of the quarter of sphere and that of the cylinders.

Define a function that builds the shelter from the centre of the sphere, the height of the cuboid and the number of tubes to place along the height.

7.4.1.8 Question 154

Redo the previous exercise but now considering the orientation of the tubes visible in the following image:

7.4.1.9 Question 155

Redo the previous exercise, but now considering the orientation of the tubes as seen in the following image:

7.4.1.10 Question 156

Consider the construction of a "sphere" of cones like the one presented in the following image:

Notice that all the cones have their vertex in the same point and are oriented so that the centre of the base lies on a virtual sphere. Notice all the "meridians" and "parallels" have the same number of cones and also note that the radius of the base of the cones diminishes as we come closer to the "poles" so that the cones do not interfere with each other.

Write a Racket program capable of building the "sphere" of cones.

7.4.1.11 Question 157

Consider a variation on the "sphere" of cones from the previous exercise in which, instead of diminishing the radius of the cones’ base as we get closer to the poles, we diminish the number of cones, as presented in the following image:

Write a Racket program able to build this cones "sphere".

7.4.1.12 Question 158

Define a Racket function able to create pierced spherical shells, as the ones presented below:

The function should have as arguments the centre, radius and thickness of the spherical shell, and the radius and number of perforations to perform along the "equator." This number should diminish as we come closer to the "poles."

7.4.1.13 Question 159

Consider the following construction built from a random arrangement of pierced spheres:

To model the previous image, define a function that receives two points which define the limits of an imaginary volume (parallel to the coordinated axes) inside which are located the centres of the spheres and the minimum and maximum value of the radius of each sphere, the thickness of the shell, the number of spheres to create and, finally, the necessary parameters to do the same type of perforations on each sphere.

Note the interior of the construction should be unobstructed, i.e., as illustrated in the following section:

7.4.1.14 Question 160

An impossible cube (also known as irrational cube) is a famous optical illusion in which a cube is presented in a way that it seems to have some edges in front of the others, in an seemingly impossible configuration. The following image shows an impossible cube.

Define a impossible-cube function that creates impossible cubes.

7.4.1.15 Question 161

Consider a cube built from an arrangement of smaller cubes, as presented in the following image:

As it is easy to see, the smaller cubes each have a third of the side of the built cube. Define a function that, from the coordinates of one vertex of the cube and the length of the side of the cube to build, creates this cube respecting the arrangement of smaller cubes that appears in the previous image.

7.4.1.16 Question 162

The Menger sponge is a known fractal discovered by the mathematician Karl Menger. The following image shows several stages of building a sponge of Menger.

Like the previous exercise, building a Menger sponge can be done through the composition of smaller cubes with the nuance of replacing the smaller cubes by (sub-)sponges of Menger. Naturally, in the case of implementing it on a computer, this infinite recursion is impracticable so, because of that, we need to establish a stopping condition, from which no more (sub-)sponges of Menger are created, but only a cube.

Define the menger-sponge function that receives the coordinate of one vertex of the sponge, the sponge dimension and the desired level of depth for the recursion.

7.4.1.17 Question 163

Consider the following cover made from domes in Roman arch:

Define a function called cover-roman-arches that has as parameters the centre of the cover, the radius of the circle that circumscribes the cover, the thickness of the cover and the number of arches to place, and produces covers as the ones presented above. Make sure your function is able to generate the following cover of only three arches:

7.5 Slice of Regions

Besides the union, intersection and subtraction operations, Rosetta provides the section operation, implemented by the function slice. This operation allows the modification of a region through its section by a plane. The specification of the plane is done through a point contained in that plane and a normal vector to the plane. The vector’s direction indicates which region we intend to discard. This figure illustrates the slicing of a cube by a (virtual) plane defined by a point \(P\) and by a vector \(\vec{N}\). This operation’s syntax is (slice region P N).

Sectioning of a solid by a slicing plan defined by a point \(P\) belonging to the plane and by a vector \(N\) normal to the plane.

As an example of using this operation let us consider the creation of a "wedge" (with an opening angle of \(\phi\)) of a sphere with a radius \(R\) centred at point \(p\). The wedge is obtained through two vertical slices on the sphere.

(define (wedge-sphere p r fi)
  (slice
   (slice
    (sphere p r)
    p (cyl 1 0 0))
   p (cyl 1 fi 0)))

This figure presents wedges with different openings, generated by the function wedge-sphere.

Wedges of a sphere. From right to left, the angle between the slicing planes varies from \(0\) to \(\pi\) in increments of \(\pi/6\).

Frequently, the slicing planes we want to employ will have normals parallel to the coordinates axes. To ease these cases, let us define the appropriate functions:

(define (u0) (xyz 0 0 0))
(define (ux) (xyz 1 0 0))
(define (uy) (xyz 0 1 0))
(define (uz) (xyz 0 0 1))
(define (-ux) (xyz -1 0 0))
(define (-uy) (xyz 0 -1 0))
(define (-uz) (xyz 0 0 -1))

Using these functions, we can easily create solids with complex shapes. For example, an eighth of a sphere is trivially obtained through three slices:

(slice
  (slice
    (slice
      (sphere (u0) 2)
      (u0) (ux))
    (u0) (uy))
  (u0) (uz))

To finish, let us consider the modelling of a tetrahedron. The tetrahedron is the simplest of the platonic solids and is characterized for being a polyhedron with four triangular sides. These four sides unite four vertices that completely specify a tetrahedron. Though Rosetta provides several operations to model some fundamental solids, such as the cuboid or the regular pyramid, it does not provide a specific operation to create tetrahedrons. To implement this operation, we can produce the tetrahedron through slices on a cuboid that completely involves the four vertices of the tetrahedron, as we present in this figure.

The creation of a tetrahedron by successive slices in an enveloping cuboid.

To specify the involving cuboid, we just need to calculate the maximum and minimum coordinate values of the tetrahedron’s vertices. Then, we slice the cuboid using the four planes matching the faces of the tetrahedron, the planes that are defined by the combination of the four vertices taken three by three. Thus, assuming the tetrahedron has the vertices \(P_0\), \(P_1\), \(P_2\) and \(P_3\), the first slice will be determined by the plane containing the points \(P_0\), \(P_1\) and \(P_2\) and preserving the part which contains \(P_3\), the second slice will be defined by the points \(P_1\), \(P_2\) and \(P_3\) and preserving \(P_0\), the third slice will be defined by the points \(P_2\), \(P_3\) and \(P_0\) and preserving \(P_1\), and the fourth by the points \(P_3\), \(P_0\) and \(P_1\) and preserving \(P_2\). Each of these planes will be specified in the slicing operation by a point and the corresponding normal vector to the plane. For calculating this normal vector to the plane, we need to use the cross product of two vectors (implemented by the pre-defined cross-c function) and we have to verify if the normal is pointing towards the point to preserve, which we can do by verifying the signal of the dot product (implemented by the pre-determined dot-c function) between the normal vector and the vector that ends at the point to preserve.

(define (normal-points p0 p1 p2 p3)
  (let ((v0 (-c p1 p0))
        (v1 (-c p2 p0)))
    (let ((n (cross-c v0 v1)))
      (if (< (dot-c n (-c p3 p0)) 0)
          n
          (*c n -1)))))
(define (tetrahedron p0 p1 p2 p3)
  (define pmin (xyz (min (cx p0) (cx p1) (cx p2) (cx p3))
                    (min (cy p0) (cy p1) (cy p2) (cy p3))
                    (min (cz p0) (cz p1) (cz p2) (cz p3))))
  (define pmax (xyz (max (cx p0) (cx p1) (cx p2) (cx p3))
                    (max (cy p0) (cy p1) (cy p2) (cy p3))
                    (max (cz p0) (cz p1) (cz p2) (cz p3))))
  (define solid (box pmin pmax))
  (set! solid (slice solid p0 (normal-points p0 p1 p2 p3)))
  (set! solid (slice solid p1 (normal-points p1 p2 p3 p0)))
  (set! solid (slice solid p2 (normal-points p2 p3 p0 p1)))
  (set! solid (slice solid p3 (normal-points p3 p0 p1 p2)))
  solid)

This figure presents the several phases of the "lapping" process of the cuboid until the tetrahedron is obtained.

7.5.1 Exercises 38
7.5.1.1 Question 164

In 1609, Johannes Kepler

Johannes Kepler was a famous mathematician and astronomer who, among many other contributions, established the laws of planetary movement.

conceived the polyhedron illustrated in the following image, which he baptised as stella octangula. This eight-arm star, also known as starry octahedron is, in fact, a composition of two tetrahedrons.

Define the function starry-octahedron which, given the centre of the cube enveloping the octahedron and the length of the side of that cube, produces the octahedron imagined by Kepler.

7.5.1.2 Question 165

The following image represents successive iterations of Sierpiński’s pyramid,

Wacław Sierpiński was a Polish mathematician who gave huge contributions to the set theory and topology. Sierpiński described a bi-dimensional version of this pyramid in 1915.

also called tetrix. The Sierpiński pyramid is a three-dimensional fractal that can be produced recursively through an imaginary tetrahedron with mid points of each edge constituting the vertices of sub-Sierpiński pyramids.

Define the sierpinski function that, from the coordinates of the four vertices and the desired level of recursion, creates the corresponding Sierpiński pyramid.

7.5.1.3 Question 166

Consider the spherical cap presented below, characterized by the two points \(P_0\) and \(P_1\) and by the diameter \(d\) of the cap’s base.

Define the spherical-cap function that receives the points \(P_0\) and \(P_1\) and the diameter \(d\) of the cap’s base. Suggestion: determine the position and dimension of the sphere and use an appropriate slicing plan.

7.6 Extrusions

We saw in the previous sections that Rosetta provides a set of pre-defined solids such as spheres, cuboids, pyramids, etc. Through the composition of those solids it is possible to model far more complex shapes but, in any case, these shapes will always be decomposable into the original basic shapes.

Unfortunately, many of the shapes that our imagination can conceive are not easy to build from just the compositions of pre-defined solids. For example, let us consider Kunst- und Ausstellungshalle, a building designed by the Viennese architect Gustav Peichl and destined to be a communication and exhibition centre. This building is of a square shape, but one of its "corners" is cut by a sinusoid, represented in this figure.

A detail of the Kunst - und Ausstellungshalle. Photograph by Hanneke Kardol.

image

Obviously, if we pretend to model the wavy wall of the Kunst - und Ausstellungshalle there will not be any pre-defined solid we can use as a starting point for its construction.

Fortunately, Rosetta provides a set of functionalities which allows us to easily solve some of these modelling problems. In this section we will consider two of those functionalities, in particular, the simple extrusion and the extrusion along a path.

7.6.1 Simple Extrusion

Mechanically speaking, extrusion is a manufacturing process which consists in forcing a moldable material through a matrix with the desired shape in order to produce parts of which section is determined by that matrix.

In Rosetta, the extrusion is an operation metaphorically identical to the one used in mechanic manufacturing, though it starts only from the matrix, which will be "extended" in the desired direction, thus producing the geometrical shape of which section is the same as the matrix. The extrusion is provided by the extrusion function which receives, as arguments, the section to extrude and the direction of the intended extrusion or the height if that direction is vertical. If the region to extrude is a curve, the extrusion will necessarily produce a surface. If the region to extrude is a surface, the extrusion produces a solid. It is important to keep in mind that there are limitations to the process of extrusion that prevent the extrusion of excessively complex shapes.

An arbitrary polygon.

As an example, let us consider this figure where an arbitrary polygon set in plane \(XY\) is shown. To create a solid with unitary height from the extrusion of this polygon in in the \(Z\) direction we can use the following expression:

(extrusion
 (surface-polygon
  (xy 0 2) (xy 0 5) (xy 5 3) (xy 13 3)
  (xy 13 6) (xy 3 6) (xy 6 7) (xy 15 7) (xy 15 2)
  (xy 1 2) (xy 1 0) (xy 0 2))
 (z 1))

The resulting shape is represented on the left in this figure

A solid (on the left) and a surface (on the right) produced by the extrusion of a polygon.

In the same figure, on the right, is represented an alternative extrusion, which instead of producing solids produces surfaces. This extrusion can be obtained by replacing the previous expression with:

(extrusion
 (polygon
  (xy 0 2) (xy 0 5) (xy 5 3) (xy 13 3)
  (xy 13 6) (xy 3 6) (xy 6 7) (xy 15 7) (xy 15 2)
  (xy 1 2) (xy 1 0) (xy 0 2))
 (z 1))

Finally, this figure illustrates two "flowers," the one on the left produced by a set of cylinders with the base set at the same place and the tops positioned along the surface of a sphere. The one on the right produced by a set of extrusions from the same horizontal circular surface, using vectors corresponding to the tops of the previous cylinders. As we can see, the extrusions of the circular surface in directions that are not perpendicular to that surface produce distorted cylinders.

A "flower" (to the left) produced by a set of bent cylinders and (to the right) produced by a set of bent extrusions.

Naturally, it is possible to extrude more complex planar shapes. For example, for the sinusoidal wall of the Kunst - und Ausstellungshalle, we can "trace" two parallel sinusoids on the plane \(XY\), we add two line segments at the extremities to transform them into a surface and, afterwards, we extrude them to the intended height, as represented in this figure.

Modelling of the Kunst - und Ausstellungshalle’s sinusoidal wall.

This description can be easily translated to Racket:

The sine-points function was defined in the section Polygonal Lines and Splines.

(let ((points-1 (sine-points (xy 0 0) 0 (* 9 pi) 0.2))
      (points-2 (sine-points (xy 0 0.4) 0 (* 9 pi) 0.2)))
  (extrusion
   (surface
     (spline points-1)
     (spline v-2)
     (spline (list (car points-1) (car points-2)))
     (spline (list (last points-1) (last points-2))))
   12))

Although the created shape presented in this figure is a reasonable approximation to the - und Ausstellungshalle’s wall, the used sinusoid is not parametrized enough for us to adapt it to other situations of sinusoidal walls.

For example, in this figure we present a detail of the facade of a building in Tucson, Arizona where the sinusoidal shape is evident, but where the sinusoid parameters are different from the ones used for the Kunst - und Ausstellungshalle.

Sinusoidal facade of a building in Tucson, Arizona. Photograph by Janet Little.

image

All these examples show the use of a sinusoidal curve but, truth be told, each of the sinusoids presented has a different parametrization. Consequently, if we intend to model all these curves, we will have to find a formula for the sinusoid curve that is parametrized enough.

Mathematically, the sinusoid equation is: \[y(x) = a \sin(\omega x + \phi)\] where \(a\) is the amplitude of the sinusoid, \(\omega\) is the angular frequency, i.e., the number of cycles by length and \(\phi\) is the phase, i.e., the "advance" or "delay" of the curve towards the \(y\) axis. This figure shows the sinusoidal curve, illustrating the meaning of these parameters.

Sinusoid curve.

The translation of the previous definition to Racket is as follows:

(define (sinusoid a omega fi x)
  (* a (sin (+ (* omega x) fi))))

From the sinusoid function, the next step to model sinusoidal curves is the definition of a function that produces a list of coordinates corresponding to the points of the sinusoid curve between the limits of an interval \([x_0,x_1]\) and separated by an increment of \(\Delta_x\). Since we might be interested in producing sinusoid curves of which the "origin" is located at a specific point in space \(p\), it is convenient that the function which creates the points of the sinusoid curve includes that point as a parameter as well:

(define (sinusoid-points p a omega fi x0 x1 dx)
  (if (> x0 x1)
    (list)
    (cons (+xy p x0 (sinusoid a omega fi x0))
          (sinusoid-points p a omega fi (+ x0 dx) x1 dx))))

NOte that the generated points are all set on a plane parallel to the \(XY\) plane, with the sinusoid evolving in a direction parallel to \(X\) axis, from a point \(p\). This figure shows three of those curves obtained through the following expressions:

(spline
  (sinusoid-points (xy 0 0) 0.2 1 0 0 (* 6 pi) 0.4))
(spline
  (sinusoid-points (xy 0 4) 0.4 1 0 0 (* 6 pi) 0.4))
(spline
  (sinusoid-points (xy 0 8) 0.6 2 0 0 (* 6 pi) 0.2))

As can be seen from analysing the figure, the sinusoids have different amplitudes and different periods.

Three sinusoidal curves.

To build a sinusoid wall we only need to create two sinusoids with a distance between them equal to the thickness \(e\) of the wall, curves which we will close to form a region that, afterwards, we will extrude to get a certain height \(h\):

(define (sinusoidal-wall p a omega fi x0 x1 dx e h)
  (let ((pts-1 (sinusoid-points p a omega fi x0 x1 dx))
        (pts-2 (sinusoid-points (+y p e) a omega fi x0 x1 dx)))
    (extrusion
     (surface
      (spline pts-1)
      (spline pts-2)
      (spline (list (car pts-1) (car pts-2)))
      (spline (list (last pts-1) (last pts-2))))
     h)))
7.6.1.1 Question 167

Consider the extrusion of a random closed curve as exemplified in the following figure:

Each of the curves is a spline produced with points generated by the random-radius-circle-points function defined in the exercise Question 133. Write an expression capable of generating a solid similar to the ones presented in the previous image.

7.6.1.2 Question 168

Write and expression capable of creating a sequence of floors with a random shape but more or less circular, as the ones presented in the following example:

7.6.1.3 Question 169

Redo the previous exercise but now in a way that the floor sequence to form a semi-sphere, as presented in the following image:

Detail of Hotel Marriott in Anaheim. Photograph by Abbie Brown.

image

7.6.1.4 Question 170

This figure shows a detail of Hotel Marriott’s facade in Anaheim. It is easy to see that the balconies on the facade are sinusoids with equal amplitude and frequency but, ever other floor, the sinusoids present opposite phases. Besides that, each balcony corresponds to a sinusoid located at a certain height \(z\).

Create a function called slab which, by receiving all the parameters needed to generate a sinusoid, creates a rectangular slab but with the facade with a sinusoidal shape, as presented in the following image:

The function should have the following parameters:

(define (slab p a omega fi lx dx ly lz)
  ...)

which are explained in the following figure:

Parameter dx represents the increment \(\Delta_x\) to consider for the creation of sinusoid’s points. The last parameter lz represents the thickness of the slab.

As an example, consider the previously represented slab as having been generated by the invocation:

(slab (xy 0 0) 1.0 1.0 0 4pi 0.5 2 0.2)

7.6.1.5 Question 171

Create a function called handrail which, by receiving all the necessary parameters to generate a sinusoid, creates a handrail with a rectangular section but along a sinusoidal curve, as presented in the following image:

The function should have the following parameters:

(define (handrail p a omega fi lx dx l-handrail a-handrail)
  ...)

The first parameters are the ones needed to completely specify the sinusoidal curve. The last two l-handrail and a-handrail correspond to the width and height of the rectangular section of the handrail.

For example, the previous image could have been generated by the invocation:

(handrail (xy 0 0) 1.0 1.0 0 4pi 0.5 0.5 0.2)

7.6.1.6 Question 172

Create a function called uprights which, by receiving the needed parameters to generate a sinusoid, creates the supporting uprights for a handrail with a sinusoid curve, as presented in the following image:

Notice that the uprights have a circular section and, therefore, correspond to cylinders with a determined height and a determined radius. Also note the uprights have a certain displacement distance dx between them.

The function should have the following parameters:

(define (uprights p a omega fi lx dx height radius)
   ldots)

The first parameters are the ones necessary to completely specify the sinusoid curve. The last two height and radius correspond to the height and radius of each cylinder.

For example, the previous image could have been generated by the invocation:

(uprights (xy 0 0) 1.0 1.0 0 4pi 0.8 1 0.1)

7.6.1.7 Question 173

Create a function called guardrail which, by receiving all the necessary parameters to create the handrail and the uprights, creates a guard rail, as shown in the following image:

To simplify, consider that the uprights should have as diameter the same width as the handrail.

The function should have the following parameters:

(define (guardrail p a omega fi lx dx
                a-guardrail l-handrail a-handrail d-upright)
   ldots)

The first parameters are the ones necessary to completely specify the sinusoid curve. The parameters a-guardrail, l-handrail, a-handrail and d-uprights are, respectively, the height of the guard rail, the width and height of the square section of the handrail and the horizontal displacement of the uprights.

For example, the previous image could have been generated by the invocation:

(guard (xy 0 0) 1.0 1.0 0 4pi 0.5 1 0.1 0.04 0.4)

7.6.1.8 Question 174

Create a function called floor which, by receiving all the necessary parameters to create the slab and the guard rail, creates a floor, as presented in the following image:

To simplify, consider the guard rail is positioned at the extremity of the slab. The function floor should have the following parameters:

(define (floor p a omega fi lx dx ly
              a-slab a-guardrail l-handrail a-handrail d-upright)
   ldots)

For example, the previous image could have been generated by the invocation:

(floor (xy 0 0) 1.0 1.0 0 2*pi 0.5 2 0.2 1 0.04 0.02 0.4)

7.6.1.9 Question 175

Create a function called building which receives all the necessary parameters to create a storey, including the height of each storey a-storey and the number of storeys n-storeys, and creates a building with those storeys, as presented in the following image:

The function building should have the following parameters:

(define (building p a omega fi lx dx ly
                a-slab a-guardrail l-handrail a-handrail d-upright
                a-story n-storeys)
   ldots)

For example, the previous image could have been generated by calling the following code:

(building (xy 0 0) 1.0 1.0 0 (* 20 pi) 0.5 20
        0.2 1 0.04 0.02 0.4 4 8)
7.6.1.10 Question 176

Modify the building function to receive an additional parameter that represents a phase increment to be considered on each floor. Through that parameter it is possible to generate buildings with more interesting facades, in which each floor has a different sinusoid shape. For example, compare the following building with the previous one:

7.6.1.11 Question 177

The following images represent some variations around the phase increment. Identify the increment used in each image.

7.6.2 Extrusion Along a Path

The extrusions done in the previous examples correspond to creating solids (or surfaces) through the dislocation of a section set at the \(XY\) plane along a certain direction. Through the sweep function, it is possible to generalize the process so that said dislocation is performed along an arbitrary curve. As it happened with the simple extrusion, it is important to keep in mind that there are certain limitation to the extrusion process which prevent the extrusion from excessively complex forms or along excessively complex paths.

As an example let us consider the creation of a cylindrical tube with a sinusoidal shape. It is relatively evident that the section to extrude is a circle, but it is also obvious that the extrusion cannot be performed over a fixed direction as the result would not have the intended sinusoidal shape. To solve this problem, instead of extruding a circle along a line, we can simply dislocate that circle along a sinusoid, obtaining the image of this figure. This image was produced by the following expressions:

A cylindrical tube with a sinusoidal shape.

(let ((section (surface-circle (xy 0 0) 0.4))
      (curve (spline (sine-points (xy 0 0) 0 (* 7 pi) 0.2))))
  (sweep curve section))

Both the extrusion along a path (sweep), and the simple extrusion (extrusion), allow the creation of countless solids, but it is important to know which of the operations is more adequate for each case. As an example, let us again consider the creation of a sinusoidal wall. In section Simple Extrusion we idealized a process of doing it from a region limited by two sinusoids, that we extruded vertically, to form a solid wall. Unfortunately, when the sinusoids used as starting point have accentuated curvatures, the resulting walls will have a thickness that is manifestly not uniform, as the image presented in this figure shows.

Top view of two overlaying sinusoid walls. The thicker profile was generated from the extrusion of a region limited by two sinusoids. The thinner profile was generated by the extrusion of a rectangular region along a sinusoid.

To give the wall a uniform thickness, it is preferable we think of the modelling of the wall as a rectangle, with the height and thickness of the wall, which moves along the curve that defines the wall. This way, to create a sinusoid wall, we just need to create the "guide" line with the shape of a sinusoid along which we will dislocate the rectangular profile of the wall through:

(define (sinusoidal-wall p a omega fi x0 x1 dx height width)
  (sweep
    (spline
      (sinusoid-points
        (+xyz p 0 (/ width 2.0) (/ height 2.0))
        a omega fi x0 x1 dx))
    (surface-rectangle p height width)))

The previous function easily allows us to create the intended walls. The following expressions were used to generate the image in this figure.

(sinusoidal-wall (xy 0 0) 0.2 1 0 0 (* 6 pi) 0.4 3 0.2)
(sinusoidal-wall (xy 0 4) 0.4 1 0 0 (* 6 pi) 0.4 2 0.4)
(sinusoidal-wall (xy 0 8) 0.6 2 0 0 (* 6 pi) 0.2 5 0.1)

Three walls with different heights and thickness built over sinusoidal curves.

7.6.2.1 Question 178

Redo exercise Question 171 using an extrusion along a path.

7.6.2.2 Question 179

Define a function wall-points which, given the thickness and height of a wall and given a curve described by a list of points, builds a wall through dislocating a rectangular section with the given thickness and height through a spline that goes through the given coordinates. If needed, you can use the spline function which, from a list of points, creates a spline that goes through those points.

7.6.3 Extrusion with Transformation

The operation of extrusion along a path provided by Rosetta also allows to apply a transformation to the section as it is being extruded. The transformation consists in a rotation and in a scale factor, giving that the rotation becomes particularly useful to model shapes with torsions. Both rotation and scale factor are optional parameters of the sweep function.

As an example, let us consider the modelling of the columns presented in the facade of the building represented in this figure.

"Twisted" columns on the facade of a building in Atlanta. Photograph by Pauly.

image

Since the columns presented on the facade correspond to cuboids which suffered a torsion of \(90^o\), we can simulate this shape trough using a rectangular section that is rotated the same \(90^o\) during the extrusion process:

(sweep
  (line (u0) (z 30))
  (surface-polygon (xy -1 -1) (xy 1 -1) (xy 1 1) (xy -1 1))
  pi/2)

To better understand the torsion effect, this figure shows the torsion process of columns with a square section in successive angle increments of \(\frac{\pi}{4}\), from a maximum torsion of \(2\pi\) clockwise to the maximum torsion of \(2\pi\) in the opposite direction.

Modelling of the torsion of square section columns. From left to right, the columns were twisted in increments of \(\frac{\pi}{4}\) radians.

% \falar de offset e, antes, do join. Explicar curvas (matematicas) % http://en.wikipedia.org/wiki/Curve como um objecto unidimensional % (não necessariamente continuamente diferenciável).

7.7 Gaudí’s Columns

Antoni Gaudí, one of the greatest architects of all times, lead the Catalan Modernism with a very personal interpretation of the Art Nouveau movement, which combined Gothic elements, surrealism elements and oriental influences. Gaudí searched for his works to have a strong relation to Nature which was his main source of inspiration. In his architecture it is frequent to find references to natural elements, such as surfaces that resemble waves and support structures strongly inspired in the shape of trees.

In 1883, at the young age of 31, Gaudí started working on the Expiatory Church of the Holy Family, in Barcelona, where he explored fantastic combinations of shapes that make this temple, still unfinished, one of the most singular works in Architecture.

In this section, we will lean over a tiny part of this work, namely, the columns idealized by Gaudi, which can be partially seen in this figure.

Support columns of the Temple of Sagrada Família in Barcelona. Photograph by Piqui Cuervo.

image

As can be understood from observing the figure, Gaudí imagined columns in which the shape varies along the column. His goal was to suggest a stone "forest" and, for that, he modelled columns that branch from other columns, as if they were branches of a tree, with the union points between columns resembling "knots" of tree trunks. The variation of the shapes of the columns is perfectly visible in some of these "branches," of which the base is squared, but that, at the top, assumes a nearly circular shape. In other cases, the column terminates with a section in the shape of a star.

To model these columns, Gaudí gave off two years elaborating a constructive scheme based on the intersection and union of helical shapes produced by the "torsion" of prisms [BarriosHernandez2006309]. In the simplest case, these prisms have a square section and suffer a torsion of sixteenths of a circle in both ways, i.e., \(2\pi/16=\pi/8\).

To generalize Gaudí’s approach, let us implement a function that deals with the general case of a prism with \(n\) sides which is twisted at an arbitrary angle. For that, we will use the function surface-regular-polygon that creates a regular polygon from the number of vertices \(n\), the polygon centre \(p\), the distance \(r\) of the vertex to the point \(p\), and the angle \(\phi\) of the first vertex with the \(X\) axis.

To model the twisted prism, we will create a surface with the shape of the regular polygon and we will extrude that surface to a height \(h\) twisting it at the same time a certain angle \(\Delta_\phi\) and applying a scale factor of \(e\):

(define (twisted-prism p r n h fi dfi e)
  (sweep
    (line p (+z p h))
    (surface-regular-polygon n p r fi)
    dfi
    e))

To reproduce Gaudí’s approach, we will now produce the intersection of two of these prisms, both twisted at an angle of \(\pi/8\), but the first one in one direction and the second in the other. To obtain more realism, let us also apply a scale factor of 0.9, so that the column narrows as it goes up:

(intersection
 (twisted-prism (xy 0 0) 1 4 10 0 (/ pi 8) 0.9)
 (twisted-prism (xy 0 0) 1 4 10 0 (/ pi -8) 0.9))

The result is the column on the far left side of this figure. The union of these prisms produces another of the shapes used by Gaudí, visible immediately to the right of the previous column:

(union
 (twisted-prism (xy 5 0) 1 4 10 0 (/ pi 8) 0.9)
 (twisted-prism (xy 5 0) 1 4 10 0 (/ pi -8) 0.9))

Columns obtained by the intersection and union of twisted prisms with a square section.

As done by Gaudí, we can complement the previous columns with its natural extension, doubling the number of prisms and reducing the torsion angle to half:

(intersection
 (twisted-prism (xy 10 0) 1 4 10 0 (/ pi 16) 0.9)
 (twisted-prism (xy 10 0) 1 4 10 0 (/ pi -16) 0.9)
 (twisted-prism (xy 10 0) 1 4 10 (/ pi 4) (/ pi 16) 0.9)
 (twisted-prism (xy 10 0) 1 4 10 (/ pi 4) (/ pi -16) 0.9))
(union
 (twisted-prism (xy 15 0) 1 4 10 0 (/ pi 16) 0.9)
 (twisted-prism (xy 15 0) 1 4 10 0 (/ pi -16) 0.9)
 (twisted-prism (xy 15 0) 1 4 10 (/ pi 4) (/ pi 16) 0.9)
 (twisted-prism (xy 15 0) 1 4 10 (/ pi 4) (/ pi -16) 0.9))

The results are visible on the two columns to the right, in this figure.

7.8 Revolutions

A surface of revolution is a surface created by the rotation of a bi-dimensional curve around an axis. A solid of revolution is a solid generated by the rotation of a bi-dimensional region around an axis.

With the rotation being a very simple process of creating surfaces and solids, it is natural that Rosetta provides an operation to do it. Function revolve serves exactly that purpose. The function revolve receives, as arguments, the region to "revolve" and, optionally, a first point on the rotation axis (by omission, the origin), a second point on that rotation axis (by omission, a point above the previous one), the initial angle of revolution (by omission, zero) and the angle increment for the revolution (by omission, \(2\cdot\pi\)). Naturally, if the increment is omitted or if it is \(2\cdot\pi\) radians, we get a complete revolution.

7.8.1 Surfaces of Revolution

Using these functions it is now easier to create surfaces or solids of revolution. Let us consider, for example, the spline presented in this figure, which was created from the following expression:

(spline (xyz 0 0 2) (xyz 1 0 -1) (xyz 2 0 1)
        (xyz 3 0 -1) (xyz 4 0 0) (xyz 5 0 -1))

A spline used as the base to build a surface of revolution.

Note that the spline is located on the \(XZ\) plane and, because of that, can be used as the base for a surface of which the revolution axis is the \(Z\)axis. To better visualize the "interior" of the surface, let us consider an opening of \(\frac{2\cdot\pi}{4}\). The corresponding Racket expression is:

(revolve
  (spline (xyz 0 0 2) (xyz 1 0 -1) (xyz 2 0 1)
          (xyz 3 0 -1) (xyz 4 0 0) (xyz 5 0 -1))
  (u0)
  (uz)
  (* 1/4 2pi) (* 3/4 2pi))

The result of evaluating the previous expression is represented in this figure.

A surface of revolution generated by the spline represented in this figure.

Surfaces of revolution are often used in Architecture, namely, to design domes. The onion shaped dome, for example, is a thoroughly explored element both in Russian architecture, Mughal architecture and other architectures influenced by these ones. The image in this figure shows the domes of Verkho Saviour Cathedral in Moscow, Russia. % Na imagem seguinte,

Domes of the Verkho Saviour Cathedral in Moscow, Russia. Photograph by Amanda Graham.

image

Onion shaped domes possess an axial symmetry that allows them to be modelled as if they were surfaces of revolution. For that, we will define a function called dome-revolution which, from a list of coordinates belonging to the dome’s profile, builds the surface of revolution that models the dome. To simplify, let us admit that the dome will be closed at the top and that the top is given by the first element of the coordinates list. This simplification allows establishing the surface rotation axis from the first element of the coordinates list:

(define (dome-revolution points)
  (revolve
    (spline points)
    (car points)))

To test if the function correctly models a dome, we can get some coordinates from the profile of real domes and use them to invoke the function. That is precisely what we did in the following expressions:

(dome-revolution
  (list (xyz 0 0 12)
        (xyz 0 1 8)
        (xyz 0 6 4)
        (xyz 0 6 2)
        (xyz 0 5 0)))
(dome-revolution
  (list (xyz 15 0 9)
        (xyz 15 3 8)
        (xyz 15 7 5)
        (xyz 15 8 3)
        (xyz 15 8 2)
        (xyz 15 7 0)))
(dome-revolution
  (list (xyz 30 0 13)
        (xyz 30 1 10)
        (xyz 30 5 7)
        (xyz 30 5 2)
        (xyz 30 3 0)))

which create, from left to right, the surfaces presented in this figure.

Domes generated in Rosetta.

7.8.1.1 Question 180

Consider the tube with a sinusoidal profile presented in the following image.

The tube was produced taking into account the geometrical parameters described in the following profile:

Define the function sinusoidal-tube which, from the previous parameters \(P\), \(r\), \(a\), \(omega\), \(fi\), \(lx\) and, finally, from the separation between interpolation points \(\Delta_x\), generates the intended sinusoidal tube. For example, the tubes represented in the following image were generated by the evaluation of the following expressions:

(sinusoidal-tube (xyz -20 80 0) 20 2.0 0.5        0 60 0.2)
(sinusoidal-tube (xyz   0 30 0)  5 0.2 2.0        0 60 0.1)
(sinusoidal-tube (xyz 30  0 0) 10 1.0 1.0 (/ pi 2) 60 0.2)

7.8.1.2 Question 181

Consider an egg as a generalization of eggs of different proportions, as illustrated in the following image:

The egg, though a three-dimensional object, has a section as sketched in the following image:

Define an egg function that creates an egg. The function should receive, only, the coordinates of point \(P\), the \(r_0\) and \(r_1\) radii and, finally, the height \(h\) of the egg.

7.8.2 Solids of Revolution

If, instead of using a curve to produce a surface of revolution, we use a region, we will produce a solid of revolution. As an example, let us explore the use of a trefoil to generate an arch. The image on the left of this figure shows the region used as a starting point and this figure shows the created solid. For this example, we used the following expression:

A solid of revolution generated by the trefoil represented in this figure.

(revolve
  (n-folio (xy 3 0) 1 3)
  (xyz-from-normal (u0) (uy))
  0 pi)

Through the combination of extrusions and revolutions, it is possible to produce very sophisticated models. Let us consider, for example, the modelling of an arcade of which the columns and arches have sections in the shape of a n-folio. To model these shapes it is necessary to know the coordinates \(p\) of the centre of the foil used to generate the column or the arch, the radius \(r_c\) of the columns and the \(n_f\) number of foils to use. For the columns it is also necessary to know the height \(h\) and, finally, for the arch it is necessary to know its radius \(r_a\). The following functions implement those shapes:

(define (folio-column p rc nf h)
  (extrusion (n-folio p rc nf) h))
(define (folio-arch p rc ra nf)
  (revolve
    (n-folio p rc nf)
    (+x p ra) (+xy p ra 1)
    0 pi))

To build the arcade, the simplest way is to define a function that builds a column and an arch and that, recursively, builds the remaining arcades until it has no more arcades to build, the case at which it creates the last column that supports the last arch. The translation of this algorithm to Racket is:

(define (folio-arcade p rc nf ra h n)
  (if (= n 0)
    (folio-column p rc nf h)
    (union
     (folio-column p rc nf h)
     (folio-arch (+xyz p 0 0 h) rc ra nf)
     (folio-arcade (+xyz p (* 2 ra) 0 0)
                    rc nf ra h (- n 1)))))

The image of this figure presents a perspective on a series of arcades generated by the following expressions:

(folio-arcade (xy 0  0) 1  4 5 10 5)
(folio-arcade (xy 0 10) 1  6 5 10 5)
(folio-arcade (xy 0 20) 1  8 5 10 5)
(folio-arcade (xy 0 30) 1 10 5 10 5)
(folio-arcade (xy 0 40) 1 12 5 10 5)

Arcades generated by the folio-arcade function. From the front to the back, the arcades have quatrefoil, exafoil, octafoil, decafoil and dodecafoil as section.

7.8.2.1 Question 182

We want to create a program capable of generating a barrel with the base located on the \(XY\) plane. Consider that the base of the barrel is closed but the top is open.

Create a function called profile-barrel that receives the point \(P\), the radii \(r_0\) and \(r_1\), the height \(h\) and the thickness \(e\), and returns the region that defines the profile of the barrel, as presented in the following image:

7.8.2.2 Question 183

Using the function profile-barrel, define the function barrel which, having the same parameters as the previous one, creates the three-dimensional barrel. The function should have the following form:

(define (barrel p r0 r1 h e)
   ldots)

As an example, consider that the following image was generated by the evaluation of the following expressions:

(barrel (xyz 0 0 0) 1 1.3 4 0.1)
(barrel (xyz 4 0 0) 1 1.5 3 0.3)
(barrel (xyz 8 0 0) 1 0.8 5 0.1)

7.8.2.3 Question 184

The barrel created in the previous exercise is excessively "modern," not representing the traditional wooden barrels that are built from wooden boards put together next to each other. The following image shows a few examples of these wooden boards, with different dimensions and angular placements:

Define the function board-barrel which, besides the same parameters that define a barrel, given the initial rotation angle \(\alpha\), that defines where the board begins, and given the angle increment \(\Delta_\alpha\), which corresponds to the angular amplitude of the board, builds the three-dimensional section of the barrel corresponding to the board in question.

7.8.2.4 Question 185

Define a function called boards-barrel that receives as parameters the point \(P\), the radii \(r_0\) and \(r_1\), the height \(h\), the thickness \(e\), the \(n\) number of boards and the angular "gap" \(s\) between boards, and creates a three-dimensional barrel with that number of boards. The following image shows some examples of these barrels and was created by the evaluation of the following expressions:

(boards-barrel (xyz 0 0 0) 1 1.3 4 0.1 10 0.05)
(boards-barrel (xyz 4 0 0) 1 1.3 4 0.1  4 0.5)
(boards-barrel (xyz 8 0 0) 1 1.3 4 0.1 20 0.01)

7.9 Sections Interpolation

Section interpolation, also known as shape morphing, allows the creation of a three-dimensional object through the interpolation of planar sections of that object. In Rosetta, the section interpolation is obtained through the loft function or through one of its variants. These functions operate from an ordered list of the planar sections (that might be curves or regions) which, to minimize possible mistakes in the interpolation process, can be optionally complemented with guidelines. Guidelines are particularly important in the case of planar sections with rectilinear parts.

In the next sections we will analyse separately each of these forms of interpolation.

7.9.1 Interpolation by Sections

Let us start with interpolation using only sections. There are two fundamental ways of doing this interpolation: using a ruled surface (i.e., surfaces generated by a dislocating line segment) between each planar section, or using a "smooth" surface (i.e., without sudden shifts in inclination) that comes closer to several sections. The interpolation by ruled surfaces is implemented by the loft-ruled function, while the interpolations by smooth surfaces is done with the loft function.

To compare the effects of these two functions, let us consider the following expressions that create the interpolation by ruled surfaces from three circles, represented on the left in this figure:

(let ((circ0 (surface-circle (xyz 0 0 0) 4))
      (circ1 (surface-circle (xyz 0 0 2) 2))
      (circ2 (surface-circle (xyz 0 0 4) 3)))
  (loft-ruled (list circ0 circ1 circ2)))

In the same this figure, to the right, we can find a smooth surface interpolation of the same three circles, generated by the following expressions:

(let ((circ0 (surface-circle (xyz 0 9 0) 4))
      (circ1 (surface-circle (xyz 0 9 2) 2))
      (circ2 (surface-circle (xyz 0 9 4) 3)))
  (loft (list circ0 circ1 circ2)))

Surface interpolation of three circles. To the left, using ruled surface interpolation. To the right, using smooth surface interpolation.

7.9.2 Interpolation with Guiding

It is relatively obvious that there is an unlimited number of different surfaces that interpolate two given sections. The algorithms of interpolation available in Rosetta thus have to take some options to decide which one is the real interpolation they will produce. Unfortunately, the taken options are not always the ones the user wants and it is perfectly possible (and also frequent) that the produced interpolation does not match the desired outcome, specially when it is necessary to produce an interpolation between two very different sections.

This behaviour can be seen in this figure where we present the interpolation between an hexagon and a triangle and where the lack of uniformity of the interpolation is perfectly visible.

The exact interpolation that is produced depends not only on the CAD application that is being used but also on the version of that application.

The figure on the left was generated by the following expression:

(loft
 (list
  (surface-regular-polygon
   6 (xyz 0 0 0) 1.0 0 #t)
  (surface-regular-polygon
   3 (xyz 0 0 5) 0.5 (/ pi 6) #t)))

Note, on the left side of this figure, that one of the sides of the triangle is directly mapped into one of the sides of the hexagon, forcing the interpolation to distort the remaining two sides of the triangle to map the remaining five sides of the hexagon.

Fortunately, Rosetta offers other ways of doing section interpolation that minimizes the possibility of errors, in particular, through the guided-loft function. This function receives not only the list of sections to interpolate but also a list of guiding curves that restrict the interpolation. This function allows us to solve the problem reported in this figure through establishing guidelines between the relevant vertices of each polygon. These guidelines will limit Rosetta to generate an interpolation that contains these lines on its surface and, as a consequence, serve to control the pairing of points between the sections to interpolate. For that, we will simply generate three vertices of the hexagon which, together with the three vertices of the triangle, will allow the creation of the guidelines:

Since we need to build the guidelines from the polygons’ vertices, we will define a function that, with two lists of points, creates a list of lines that unite the points two by two:

(define (guidelines p0s p1s)
  (if (null p0s)
    (list)
    (cons (line (car p0s) (car p1s))
          (guidelines (cdr p0s) (cdr p1s)))))

Next, we will simply generate three vertices of the hexagon that, together with the three vertices from the triangle, will allow the creation of the guidelines:

(guided-loft
 (list
  (surface-regular-polygon
   6 (xyz 3 0 0) 1.0 0 #t)
  (surface-regular-polygon
   3 (xyz 3 0 5) 0.5 (/ pi 6) #t))
 (guidelines
   (regular-polygon-vertices
     3 (xyz 3 0 0) 1.0 0 #t)
   (regular-polygon-vertices
     3 (xyz 3 0 5) 0.5 (/ pi 6) #t)))

The evaluation of the previous expression allows Rosetta to produce a more correct interpolation between the hexagon and the triangle, as can be seen on the right side of this figure.

Top view of the interpolation between an hexagonal section and a triangular section. On the left, the interpolation was performed without guidelines. On the right, the interpolation was performed with guidelines that associate three vertices of the hexagon equally spaced to the three vertices of the triangle.

7.9.2.1 Question 186

Consider the interpolation between two random closed curves positioned on different heights as presented in the following figure:

Each of the curves is a spline produced by points generated by the random-radius-circle-points function defined in exercise Question 133. Write an expression capable of generating a solid similar to the ones presented in the previous image.

8 Transformations

8.1 Introduction

Until now, all the objects we created had a definitive nature: the parameters used to build them define univocally the shape of those objects and all we can do is invoke the functions that create these objects with different parameters to build different objects.

Although that way of creating objects is powerful enough, there are alternatives potentially more practical that are based in the modification of previously created objects. That modification is performed through the operations of translation, rotation, reflection and scale.

It is important to note that these operations do not create new objects, they only affect those to which they are applied. For example, after applying a translation to an object, this object simply changes its position.

However, sometimes we want to apply transformations to objects that produce new objects as a result. For example, we might want to produce a translation of an object, but leaving the original object in its original place. One way of doing this will be to apply the translation operation, not to the original object, but to a copy of it. The possibility of creating copies of objects can then be used to distinguish the case where we want the operation to modify an object from the case where we want the operation to create a new object. For this purpose, Rosetta provides the copy-shape operation that receives an object as an argument and returns a copy of that object, situated in the exact same place as the original. Naturally, in the CAD tool two equal objects will appear overlapping. Typically, the copied object will subsequently be transformed, for example, by moving it to another location.

Next we will see which are the transformation operations available in Rosetta.

8.2 Translation

The translation operation moves an object by adding a vector to all its points, causing all these points to move a certain distance in a determined direction. The vector components indicate what is the displacement in relation to each coordinate axis.

To perform this operation, Rosetta provides the move operation, that receives one object and one displacement vector to perform the translation operation. As an example, we have:

(move (sphere) (xyz 1 2 3))

We should note that the function returns the object that suffered the translation to allow its simple linkage with other operations.

It is easy to see that, for the previous example, it is simpler to immediately specify which is the sphere’s centre by writing:

(sphere (xyz 1 2 3))

However, in more complex cases it can be very advantageous to consider that the objects are created at the origin and later they suffer a translation to the desired position.

One Papal Cross.

Let us consider, for example, a Papal Cross, defined by the union of three horizontal cylinders of progressively decreasing length placed along a vertical cylinder, as can be seen in this figure. It is noteworthy that all the cylinders have the same radius and that their length and position are in function of that radius. In terms of proportion, the vertical cylinder of the Papal Cross has a length equal to \(20\) radii, while the horizontal cylinders have lengths equal to \(14\), \(10\) and \(6\) radii and their axis is positioned at a height equal to \(9\), \(13\) and \(17\) radii. These proportions are implemented by the following function from a reference point \(p\):

(define (papal-cross p radius)
  (union
    (cylinder p
              radius
              (+z p (* 20 radius)))
    (cylinder (+xz p (* -7 radius) (* 9 radius))
              radius
              (+xz p (* 7 radius) (* 9 radius)))
    (cylinder (+xz p (* -5 radius) (* 13 radius))
              radius
              (+xz p (* 5 radius) (* 13 radius)))
    (cylinder (+xz p (* -3 radius) (* 17 radius))
              radius
              (+xz p (* 3 radius) (* 17 radius)))))

However, if we assume that the cross is initially positioned at the origin, we can slightly simplify the previous definition:

(define (papal-cross radius)
  (union
    (cylinder (u0)
              radius
              (z (* 20 radius)))
    (cylinder (xz (* -7 radius) (* 9 radius))
              radius
              (xz (* 7 radius) (* 9 radius)))
    (cylinder (xz (* -5 radius) (* 13 radius))
              radius
              (xz (* 5 radius) (* 13 radius)))
    (cylinder (xz (* -3 radius) (* 17 radius))
              radius
              (xz (* 3 radius) (* 17 radius)))))

Naturally, if we want to place the cross at a specific position, for example, \(1,2\), we should write:

(move (papal-cross 1) (xy 1 2))

8.3 Scale

The scale consists in a transformation that increases or decreases the dimension of an entity without changing its form. This operation is also called homothety. Although it is conceivable to have a scale operation that modifies each dimension independently, it is more usual to employ a uniform scale that modifies simultaneously the three dimensions, affecting them with the same factor. If the factor is bigger than one, the size increases. If the fact is smaller than one, the size decreases.

In the case of Rosetta, only a uniform scale operation is provided: scale. It is easy to see that the scale, besides changing the object’s dimension, can also change its position. If that case is not pretended, the obvious solution is to previously apply a translation to centre the object an the origin, then apply the scale operation and finally apply the inverse translation to "return" the object to its original position.

By using the scale operation, it is possible to further simplify the previous definition. In truth, because the cross’s dimension depends only on the radius, we can arbitrate a unitary radius which we can later change through a scale operation. That way, we can write:

(define (papal-cross)
  (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))))

If we want to build a Papal Cross with a determined radius \(r\), for example, \(r=3\), we need only write:

(scale (papal-cross) 3)

8.4 Rotation

In the rotation process, all the object’s points move in a circular motion in turn of a point (two dimensions) or an axis (three dimensions). In the general case of a three dimensional rotation it is usual to decompose this into three successive rotations around the coordinate axes. These rotations around the axes X, Y and Z are called main rotations, by analogy with the concept of main axes that applies to X, Y and Z. Each of these rotations is performed by the rotate function that receives, as arguments, the object on which the rotation is applied, the rotation angle, and two points that define the rotation axis. By omission, those points are the origin and a point above the previous, which implies that, by omission, the rotation will be performed in relation to the Z axis.

This figure illustrates some combinations of translations, scales and rotations, generated by the following program:

(papal-cross)
(move (papal-cross) (x 20))
(move (scale (papal-cross) 1.25) (x 40))
(move (rotate (scale (papal-cross) 1.5) pi/4) (x 60))
(move (rotate (scale (papal-cross) 1.75) pi/4 (u0) (ux)) (x 80))

From left to right, a Papal cross of unitary radius placed at the origin, followed by a translation, followed by a scale with translation, followed by a scale with rotation and translation and, finally, scale with rotation around the X axis and translation.

8.5 Reflection

In addition to the translation, scale and rotation, Rosetta also implements the reflection operation, providing for that, the function mirror. This function receives, as arguments, the shape to reflect, a reflection plane described by a point contained in the plane and a normal vector to the plane. By omission, the reflection plane is the XY plane.

As an example, let us consider the hourglass shown in this figure that has as parameters the hourglass base centre point, the hourglass base radius, the hourglass strangulation radius and the hourglass height.

A hourglass

It is not difficult to conceive this shape as the union of two cone frustums:

(define (hourglass p rb rc h)
  (union
    (cone-frustum p rb (+z p (/ h 2)) rc)
    (cone-frustum (+z p (/ h 2)) rc (+z p h) rb)))

However, it is possible we simplify the previous definition through the use of the mirror operation:

(define (hourglass p rb rc h)
  (mirror (cone-frustum p rb (+z p (/ h 2)) rc)
          (+z p (/ h 2))))

In the following section we will see an example where these operations will be used for modelling one of the most famous buildings in the world.

8.6 The Sydney Opera House

The Sydney Opera House resulted from an international competition launched in 1957 for the design of a building dedicated to performances. The winner was the project by Jørn Utzon, a Danish architect until then little known. His project, even though it did not fully fulfil the competition requirements, was selected by the famous architect Eero Saarinen, then a member of the jury, that immediately considered it as a landmark project. The proposal consisted in a set of structures, shaped like a shell, capable of accommodating various concert halls. The result of this final proposal is represented in this figure.

The Sydney Opera House. Photograph by Brent Pearson.

image

Clearly innovative, Utzon’s design was too advanced for the construction and project design technologies of the time and was by many considered impossible. In the three years that followed the project’s approval, Utzon, along with the structural engineering team of the company Ove Arup, tried to find a mathematical formulation for his hand-drawn shells, having experimented a variety of different approaches, including parabolic, circular and elliptical shapes, but all of the solutions had, besides enormous technical difficulties, very high costs which were completely incompatible with the approved budget.

In the summer of 1961, Utzon was near the brink of despair and decided to dismantle the shells’ perspex model. However, upon packing the shells away, he found out they fit almost perfectly inside each other, which would only be possible if the different shells had the same curvature at all its points. Now, the surface that has the same curvature at all its points is, obviously, the sphere, which led Utzon to think that maybe it would be possible to shape his shells as "cut" triangles on a sphere’s surface. Although this design was not exactly identical to the original drawings, it had the advantage of being calculable in computers and, more importantly still, to allow its economic construction. Ove Arup’s collaboration was crucial for Utzon’s idea to be put to practice but the genius idea that solved the problems of its construction, as well as the original design, belongs to Utzon. Utzon’s idea is explained in a bronze model placed next to the building of the Opera House, as can be see in this figure.

Commemorative plate that explains Utzon’s idea for modelling the shells. Photography by Matt Prebble, UK,December 2006.

image

Unfortunately, construction delays and the ever accumulating costs, led the government to start questioning the political decision to build the opera house and forced Utzon to resign when the construction of the interiors was not yet finished. Utzon was devastated and left Australia in 1966, never to return. Against the wishes of most architects, the masterpiece was completed by Peter Hall and inaugurated in 1973 without a single reference made ​​to Utzon. Unfortunately, the work of Peter Hall was not at the same level as Utzon’s and the contrast between the stunning exteriors and simple interiors led the work to be considered a "semi-masterpiece".

Despite Utzon’s personal drama, this story turned out to have a happy ending: thirty years later, the Australian government remodelled the Sydney Opera House to become the true masterpiece recognition that it deserved and got Utzon, who never got to see his work finished, to accept directing the work aiming to give back its original appearance.

In this section, we will model the shells of the Sydney Opera House following exactly the same solution proposed by Utzon. All the building shells will be modelled by spherical triangles obtained by three cuts in a sphere. This figure shows two spheres of equal radius from which we cut a triangle in each, so as to get two of the half-shells that constitute the Sydney Opera House.

Two of the half-shells that constitute the Sydney Opera House overlapping the spheres from where they were obtained by a succession of cuts.

To define the sections we can consider that the building will be aligned in a direction parallel to the \(Y\) axis, so the symmetry axis will therefore corresponds to a section plane of which the normal is the \(X\) axis, as an be seen in this figure. The two remaining section planes will have normals determined so as to approximate, as rigorously as we can, the volumes imagined by Utzon. As is also visible in this figure, each shell is extracted from a sphere with a fixed radius but centred at different points. Thus, to model these half-shells we are going to define a function that, from the centre \(p\) of the sphere of radius \(r\) and the normals \(n_1\) and \(n_2\) of the section planes, produces a spherical shell with thickness \(e\) and with the desired shape.

Top view of two of the half-shells that constitute the Sydney Opera House overlapping the spheres from which they were obtained by a succession of cuts.

(define (half-shell p r e n1 n2)
  (move
   (slice
    (slice
     (slice
      (subtraction
       (sphere (u0) r)
       (sphere (u0) (- r e)))
      (u0) n2)
     (u0) n1)
    (*c (-ux) (cx p)) (-ux))
   p))

As an example, this figure shows a half shell generated by evaluating the following expression:

(half-shell (xyz -45 0 0) 75 2 (sph 1 2.0 4.6) (sph 1 1.9 2.6))

A half shell of the Sydney Opera House.

To produce a complete shell we only have to apply a reflection to the half-shell, according to the vertical cut plane:

(define (shell p r e n1 n2)
  (mirror
   (half-shell p r e n1 n2)
   (u0) (-ux)))

This figure shows the shell generated by evaluating the following expression:

(shell (xyz -45 0 0) 75 2 (sph 1 2.0 4.6) (sph 1 1.9 2.6))

A Sydney Opera House shell.

To define the set of shells of a building we will use values that approximate the produced shells with Utzon’s original drawing:

(define (shells-sydney)
  (union
   (list
    (shell (xyz -45.01 0.0 0.0) 75 2
            (sph 1 1.9701 4.5693) (sph 1  1.9125 2.5569))
    (shell (xyz -38.92 -13.41 -18.85) 75 2
            (sph 1 1.9314 4.5902) (sph 1  1.7495 1.9984))
    (shell (xyz -38.69 -23.04 -29.89) 75 2
            (sph 1 1.9324 4.3982) (sph 1  1.5177 1.9373))
    (shell (xyz -58.16 81.63 -14.32) 75 2
            (sph 1 1.6921 3.9828) (sph 1  1.4156 1.9618))
    (shell (xyz -32.0 #i73.0 #i-5.0) 75 2
            (sph 1 #i0.91 4.1888) (sph 1  0.8727 1.3439))
    (shell (xyz -33.0 #i44.0 -20.0) 75 2
            (sph 1 #i1.27 4.1015) (sph 1  1.1554 1.2217)))))

Invoking this function will produce a row of shells presented in this figure.

A row of shells of the Sydney Opera House.

In order to facilitate the positioning of the building, we will further include a rotation around the \(Z\) axis, a scaling, and a final translation applied to each set of shells. Since the building has two sets of shells, we will call this function half-opera-sydney:

(define (half-opera-sydney rot-z esc trans-x)
  (move
   (scale
    (rotate
     (shells-sydney)
     rot-z)
    esc)
   (x trans-x)))

Finally, we can model the entire opera by making a building composed by a set of shells at a scale of \(1.0\) and a second building as a reduced, rotated and shifted version of the first. The scale used by Utzon was \(80\%\) and the rotation angles and the translations that we are going to use are those that allow us a high resemblance to the actual building as it stands:

(define (opera-sydney)
  (half-opera-sydney 0.1964 1.0 43)
  (half-opera-sydney -0.1964 0.8 -15))

The final result of modelling the shells is shown in this figure.

The complete model of shells of the Sydney Opera House.

8.6.1 Exercises 39
8.6.1.1 Question 187

Define a function that creates a link of a chain such as the one shown to the left of the following image.

To simplify the modelling process, consider the link as decomposable into fourths of a link, as it is shown to the right of the previous image. This way, it will be enough to define a function that creates a fourth of a link (at the cost of a quarter of a torus and a half-cylinder), and then apply a double reflection in the \(X\) and \(Y\) axes to compose the complete link.

The function should receive, as parameters, the radius \(r_e\) of the link, the radius \(r_i\) of the "wire" and length \(l\) between the semi-circles, such as shown in the following diagram.

8.6.1.2 Question 188

Define a function capable of creating chains such as those presented below:

Note that, as the chain is constructed, the links suffer successive rotations around the \(X\) axis.

8.6.1.3 Question 189

Define a function to create closed chains such as the one presented bellow:

Note that, as the chain is constructed, the links suffer successive rotations around the \(X\) axis.

9 Higher-Order Functions

9.1 Introduction

We have been demonstrating how the Racket language can enable us, through the definition of appropriate functions, to create the architectural forms that we have in mind. One of the advantages of the definition of these functions is that many of these architectural forms become parameterized, allowing us to easily try variations until we reach the numerical values of the parameters that satisfy us.

Despite the "infinite" variability that the parameters allow us, there will always be limits to what we can do with only numerical parameters, and for us to have a superlative degree of variability, we will also have to vary the actual functions.

In this section we will discuss higher-order functions. A higher-order function is a function that receives functions as arguments or a function that returns functions as result. Apart from this feature a higher-order function is no different from any other function.

9.2 Curvy Facades

To motivate the discussion let us consider a simple problem: we intend on idealizing a building where three of the sides are planar and the fourth — the facade — is a curvilinear vertical surface. To start, we can consider that this curvilinear surface has a sinusoidal shape.

As seen in section Extrusions, a sinusoid is determined by a set of parameters, such as the amplitude \(a\), the number of cycles per length unit \(omega\) and the phase \(phi\) of the curve in relation to the \(y\) axis. With these parameters the sinusoid curve equation has the form: \(y(x) = a \sin(\omega x + \phi)\)

To calculate the points of a sinusoid we can use the function sinusoid-points that computes a list of coordinates \((x,y)\) corresponding to the evolution of a sinusoid between the limits of a range \([x_0, x_1]\), with an increment \(\Delta_x\);

(define (sinusoid-points p a omega fi x0 x1 dx)
  (if (> x0 x1)
    (list)
    (cons (+xy p x0 (sinusoid a omega fi x0))
          (sinusoid-points p a omega fi (+ x0 dx) x1 dx))))

The previous function generates a list with the point of the facade. In order to model the rest of the building we need to join these points to form a curve and we need to join the three rectilinear sides in order to form a region that corresponds to the building floor plan. For this, we will connect the points with a spline and form a region between it and the lines that delimit the other three sides of the building. The length \(l_x\) of the building will be given by the horizontal distance between the two ends of the curve of the facade. The width \(l_y\) and height \(l_z\) will have to be parameters. Thus, we have:

(define (building points ly lz)
  (let ((p0 (car points))
        (p1 (last points)))
    (let ((lx (- (cx p1) (cx p0))))
      (extrusion
        (surface
          (spline points)
          (line p0 (+xy p0  0 ly) (+xy p0 lx ly) p1))
       lz))))

To visualize the capabilities of the previous function we can build several buildings using different values for the sinusoid’s parameters. In the following expressions we considered two rows of buildings, all with \(15\) meters long, \(10\) meters wide and \(20\) or \(30\) meters tall, depending on the row. This figure shows these buildings for different values of the parameters a, omega and fi:

Urbanization of buildings of which the facade corresponds to sinusoid walls with different parameters.

(building (sinusoid-points (xy  0  0) 0.75 0.5 0 0 15 0.4) 10 20)
(building (sinusoid-points (xy 25  0) 0.55 1.0 0 0 15 0.2) 10 20)
(building (sinusoid-points (xy 50  0) 0.25 2.0 0 0 15 0.1) 10 20)
(building (sinusoid-points (xy  0 20) 0.95 1.5 0 1 15 0.4) 10 30)
(building (sinusoid-points (xy 25 20) 0.85 0.2 0 0 15 0.2) 10 30)
(building (sinusoid-points (xy 50 20) 0.35 1.0 0 1 15 0.1) 10 30)

Unfortunately, although the sinusoid-points function is very useful for modelling a multitude of different buildings with a sinusoidal facade, it is totally useless to model buildings of which the facade follows a parabola or a logarithm or an exponential or, in fact, any other curve which is not reducible to a sinusoid. In fact, although the adjustment of the parameters allows us infinite variability of the modelled building, even then it will always be a particular case of a sinusoidal facade.

Naturally nothing prevents us from defining other functions for modelling different facades. Let us imagine, for example, that we want a facade with the curvature of a parabola. The parabola with vertex at \((x_v,y_v)\) and focus point at \((x_v,y_v+d)\), with \(d\) being the distance from the vertex to the focus point, is defined by the following equation:

\[(x - x_v)^2 = 4d(y - y_v)\]

that is

\[y=\frac{(x - x_v)^2}{4d}+y_v\]

In Racket, this function is defined as:

(define (parabola xv yv d x)
  (+ (/ (expt (- x xv) 2)
        (* 4 d))
     yv))

In order to generate the parabola’s points we have, as usual, to iterate over a range \([x_0, x_1]\) with a certain increment \(Delta_x\):

(define (parabola-points p xv yv d x0 x1 dx)
  (if (> x0 x1)
    (list)
    (cons (+xy p x0 (parabola xv yv d x0))
          (parabola-points p xv yv d (+ x0 dx) x1 dx))))

The following expressions test this function with different values of the various parameters:

(building (parabola-points (xy  0  0) 10  0  5 0 15 0.5) 10 20)
(building (parabola-points (xy 25  0)  5  0  3 0 15 0.2) 10 20)
(building (parabola-points (xy 50  0)  7  1 -2 0 15 0.1) 10 20)
(building (parabola-points (xy  0 20)  8  0  2 0 15 0.4) 10 30)
(building (parabola-points (xy 25 20)  6 -2  3 0 15 0.2) 10 30)
(building (parabola-points (xy 50 20)  5  0  6 0 15 0.1) 10 30)

generating the "urbanization" represented in this figure.

Urbanization of buildings of which the facade corresponds to paraboloidal walls with different parameters.

Once more, the parametrization of the function parabola-points allows us to generate an infinity of buildings of which the facade has the curvature of a parabola, but they will always be different buildings from those that we can create with the sinusoid-points function. Although the modelling process used is absolutely identical in both cases, the applied base functions—sinusoid vs parabola will always produce different curves, regardless of the particular parameters used for each one.

Clearly, if we now want to create buildings with a curved facade determined by a function \(f(x)\) that is neither a sinusoid nor a parabola, we can not employ the previous functions. First, we will need to define the function f that implements that curve and, second, we will need to define the f-points function that generates the points of f in an interval. This second part seems overly repetitive and, indeed, it is. We simply have to observe the sinusoid-points and parabola-points functions to conclude that they are very similar to each other. Logically, the same will happen with the new f-points function.

This repetition leads us to consider the possibility of abstracting the processes in question so that we are not obliged to repeat definitions that only vary in small details. For this, we can start by comparing the definitions of the functions and understand where the differences are. For example, in the case of the sinusoid-points and parabola-points functions, we have:

(define (sinusoid-points p a omega fi x0 x1 dx)
  (if (> x0 x1)
    (list)
    (cons (+xy p x0 (sinusoid a omega fi x0))
          (sinusoid-points p a omega fi (+ x0 dx) x1 dx))))
(define (parabola-points p xv yv d x0 x1 dx)
  (if (> x0 x1)
    (list)
    (cons (+xy p x0 (parabola xv yv d x0))
          (parabola-points p xv yv d (+ x0 dx) x1 dx))))

We marked in italic the differences between the two functions, and as we can see, the differences are in the parameters’ names and the name of the function that is invoked: sinusoid in the first case and parabola in the second. As the names of the parameters of a function are only visible in the function’s body (i.e., are local to the function), we can rename them as long as we do it consistently. This means that the following definitions would have exactly the same behaviour:

(define (sinusoid-points p \(\alpha\) \(\beta\) \(\gamma\) x0 x1 dx)
  (if (> x0 x1)
    (list)
    (cons (+xy p x0 (sinusoid \(\alpha\) \(\beta\) \(\gamma\) x0))
          (sinusoid-points p \(\alpha\) \(\beta\) \(\gamma\) (+ x0 dx) x1 dx))))
(define (parabola-points p \(\alpha\) \(\beta\) \(\gamma\) x0 x1 dx)
  (if (> x0 x1)
    (list)
    (cons (+xy p x0 (parabola \(\alpha\) \(\beta\) \(\gamma\) x0))
          (parabola-points p \(\alpha\) \(\beta\) \(\gamma\) (+ x0 dx) x1 dx))))

It now becomes absolutely clear that the only difference between the two functions lies in an invocation they make, which is sinusoid in one case and parabola in the other.

Now, when two functions differ only in a name they use internally, it is always possible to define a third function that generalizes them, simply transforming that name into an additional parameter.

Suppose then that we make the following definition, where f is this additional parameter:

(define (function-points p f alpha beta gamma x0 x1 dx)
  (if (> x0 x1)
    (list)
    (cons (+xy p x0 (f alpha beta gamma x0))
          (function-points p f alpha beta gamma (+ x0 dx) x1 dx))))

The innovative look of the function-points function lies in the fact that it receives a function as an argument, a function which will be associated to the parameter f. When, in the body of the function-points function, a call is made of the f function, we are, actually, calling the function that has been passed as argument to the f parameter.

Thus, the expression

(sinusoid-points p a omega fi 0 lx dx)

is absolutely identical to

(function-points p sinusoid a omega fi 0 lx dx)

Similarly, the expression

(parabola-points p xv yv d 0 lx dx)

is absolutely identical to

(function-points p parabola xv yv d 0 lx dx)

More important than the fact that we can dismiss the sinusoid-points and parabola-points functions is the fact that, now, we can model buildings of which the facades follow any other curve we want. For example, the function that describes the damped oscillatory motion has the definition:

\[y=ae^{-bx}\sin(cx)\]

or, in Racket:

(define (damped-oscillatory a b c x)
  (* a (exp (- (* b x))) (sin (* c x))))

Using the function-points function, it is now trivial to define a building of which the facade follows the curve of the damped oscillatory motion. For example, the following expression

(building
 (function-points (xy 0 0)
                damped-oscillatory
                -5 0.1 1
                0 40 0.4)
 10
 20)

produces, as a result of its evaluation, the building presented in this figure.

A building with a facade that follows the curve of the damped oscillatory motion.

9.3 Higher-Order Functions

The function-points function is an example of a class of functions which we call higher-order. A higher-order function is a function that receives other functions as arguments or returns other functions as result. In the case of the function-points function it receives a function of a parameter that will be called in successive interval points, producing the list of the found coordinates.

Higher-order functions are important tools for abstraction. They allow the abstraction of computations in which there is a part of the computation that is common and one (or more parts) that vary from case to case. Using a higher-order function, we only implement the part of the computation that is common, leaving the variable parts of the computation to be implemented as functions that will be passed in the parameters of the higher-order function.

The concept of higher-order function exists for a long time in mathematics, although rarely explicitly mentioned. Let us consider, for example, a function which sums the squares of all integers between \(a\) and \(b\), \(\sum_{i=a}^{b}i^2\):

(define (square-sum a b)
  (if (> a b)
    0
    (+ (sqr a)
       (square-sum (+ a 1) b))))
> (square-sum 1 4)

30

Let us now consider another function that sums the square roots of all the integers between \(a\) and \(b\), \(\sum_{i=a}^{b}\sqrt{i}\):

(define (square-roots-sum a b)
  (if (> a b)
    0
    (+ (sqrt a)
       (square-roots-sum (+ a 1) b))))
> (square-roots-sum 1 4)

6.146264369941973

The mere observation of the functions’ definition shows that they have a common structure characterized by the following definition:

(define (sum-??? a b)
  (if (> a b)
    0
    (+ (??? a)
       (sum-??? (+ a 1) b))))

Now, this definition is no more than a sum of a mathematical expression between two limits, i.e., a summation \(\sum_{i=a}^{b}f(i)\). The summation is a mathematical abstraction for a sum of numbers described by a mathematical expression relative to the summation index which ranges from the lower to the upper limit. The mathematical expression is, therefore, a function of the summation index.

The symbol ??? represents the mathematical expression to perform inside the sum and that we will simply transform into a parameter, by writing:

(define (summation f a b)
  (if (> a b)
    0
    (+ (f a)
       (summation f (+ a 1) b))))

We can now trivially evaluate different summations:

>

#<procedure:>>

(summation sqr 1 4)

30

30

30

>

#<procedure:>>

(summation sqrt 1 4)

6.146264369941973

6.14626

6.14626

Because it accepts a function as an argument, the sum is a higher-order function. The derivative of a function is another example. The derivative \(f'\) of a function \(f\) is a function that receives another function \(f\) as argument and returns another function—the derivative function of \(f\)as a result.

Generally, higher-order functions show up because there are two or more functions with a similar structure. In fact, the repetition of a pattern along two or more functions is a strong indicator of the need for a higher-order function.

9.4 Anonymous Functions

The possibility of using higher-order functions opens up a huge range of new applications. For example, if we want to calculate the summations \[\sum_{i=1}^{10}i^2+3i\] \[\sum_{i=1}^{100}i^3+2i^2+5i\] we need not do more than to define the functions \(f_1(x)=x^2+3x\) and \(f_2(x)=x^3+2x^2+5x\) and use them as argument of the summation function:

(define (f1 x)
  (+ (* x x) (* 3 x)))
(define (f2 x)
  (+ (* x x x) (* 2 x x) (* 5 x)))
> (summation f1 1 10)

550

> (summation f2 1 100)

26204450

As can be seen, we can now easily calculate summations of different functions. However, there is a negative aspect to note: if, for each summation we intend on calculating, we have to define the function in question, we will "pollute" our Racket environment with countless definitions of functions of which their utility will be, at most, to serve as argument to calculate the corresponding summation. Since each function must be associated with a name, we will also have to come up with names for all of them, which could be difficult, particularly, when we know that these functions are useful for nothing else. In the last two examples this problem was already visible: the names f1 and f2 only reveal the difficulty in finding more expressive names.

In order to solve this problem, we should examine in greater detail the concept of defining a function. So far we have said that, for defining a function, we have to use a combination that begins with the word define, followed by a list in which the first element is the name of the function to define and of which the remaining elements are the function’s parameters, followed by the function’s body. For example, for the function \(x^2=x\times x\), we write:

(define (sqr x) (* x x))

We have seen that after the previous definition, the sqr symbol becomes associated with a function:

> sqr

#<procedure:sqr>

However, we saw in section ?? that in order to define constants we should use the define operator in a combination that includes the name we want to define followed by the expression that we intend to associate with that name. For example:

(define fi (/ (+ 1 (sqrt 5)) 2))

We also saw that after the previous definition, the fi symbol becomes associated with a value:

> fi

1.618033988749895

By analysing these examples we can conclude that the only difference between a function definition and a definition of a constant comes down to how the defined value is obtained in one case and the other. In the definition of constants, the value is the result of the evaluation of an expression. In the definition of a function, the value is a function that is constructed from the description of a list of parameters and the body of a function. This leads us to think that if it were possible to have an expression of which the evaluation produced a function, then it would be possible to define functions as if we were defining constants. For example, in the case of the function sqr, if \(\lambda\) is that expression, it should be possible to write (define sqr \(\lambda\)).

If the expression (define sqr \(\lambda\)) associates to the symbol sqr the function created by the evaluation of the expression \(\lambda\), what will be then the evaluation of the expression \(\lambda\)? Obviously, it has to be a function, but, as it is not yet associated with any name, it is what is called as unnamed function or anonymous function. Note that an anonymous function is a function like any other, but has the particularity of not being associated with any name.

We are left with knowing the form of \(\lambda\) expressions. They have a similar syntax to the definition of functions, but they omit the function’s name (logically) and replace the define symbol with ... lambda, i.e.:

The use we made of the \(\lambda\) symbol was not innocent. It derives from the origins of the mathematical concept of anonymous function: \(\lambda\) calculus, a mathematical model for computable functions, i.e., functions whose call can be evaluated mechanically.

(lambda (parameter1 ... parametern)
  exprn+1
  exprn+2
  ...
  exprn+m)

Any expression with the previous form, when evaluated, returns an anonymous function. These expressions are designated, in Racket, as lambda expressions. Let us now notice the following:

> (lambda (x) (* x x))

#<procedure>

> (define sqr (lambda (x) (* x x)))
> (sqr 3)

9

As you can seen, the evaluation of the lambda expression returns something of which the external representation indicates that a procedure was created. When we associate the anonymous function with a name, that name becomes the designation of the function, and can be used as if it had been originally defined as a function. In practice, the definition of functions using the define operator

(define (name parameter1 ... parametern)
  exprn+1
  exprn+2
  ...
  exprn+m)

is equivalent to:

(define name (lambda (parameter1 ... parametern)
  exprn+1
  exprn+2
  ...
  exprn+m))

This equivalence is easily tested with the redefinition of functions that, now, can be defined as the creation of an association between a name and an anonymous function. The previous example demonstrated this possibility for the sqr function but it exists for any other function. For example, the function that calculates the area of a circle can be defined by either:

(define (circle-area radius)
  (* pi (sqr radius)))

or by:

(define circle-area
  (lambda (radius)
    (* pi (sqr radius))))

Although from the syntactic point of view, the form (define (f ...) ...) is equivalent to (define f (lambda (...) ...)), there is a semantic difference with important implications: the first form does not evaluate any of its arguments, whereas the latter form evaluates its second argument. This allows the second form to define functions with more sophisticated forms.

The use of anonymous functions has yet another advantage that becomes evident when we analyse the function-points function:

(define (function-points p f alpha beta gamma x0 x1 dx)
  (if (> x0 x1)
    (list)
    (cons (+xy p x0 (f alpha beta gamma x0))
          (function-points p f alpha beta gamma (+ x0 dx) x1 dx))))

As we have seen, in order to generate a list of points of, for example, a sinusoid, we can invoke this function the following way:

(function-points (xy 0 0)
                 sinusoid 0.75 0.5 0
                 0 15 0.4)

Note that, for having been defined as a generalization of the functions sinusoid-points and parabola-points, the function function-points, in addition to having introduced the f function as a parameter, it also generalized the a, omega and fi parameters of the function sinusoid-points and xv, yv and d of the function parabola-points, calling them, respectively, alpha, beta and gamma. It so happens that these parameters never change during the recursive calls performed by the function-points function. In fact, the function passes these parameters from recursive call to recursive call only because they are necessary for the invocation of the f function. Let us now imagine that, instead of passing these parameters, they were associated to the f function itself that we had just passed. In this case, we could rewrite the function-points function in the following form:

(define (function-points p f x0 x1 dx)
  (if (> x0 x1)
    (list)
    (cons (+xy p x0 (f x0))
          (function-points p f (+ x0 dx) x1 dx))))

With this new definition, the previous call would have to be rewritten as:

(function-points (xy 0 0)
                 (lambda (x) (sinusoid 0.75 0.5 0 x))
                 0 15 0.4)

Although it may not seem to be a substantial gain, rewriting the function function-points has a considerable advantage: it allows the use of any base functions, regardless of the number of parameters it has: if they have only one parameter, they can be used directly, otherwise we can "wrap" them with an anonymous function of one parameter only that invokes the desired function with all the required arguments. This makes the function function-points even more generic, as it is visible in following examples:

(spline
  (function-points (xy 0 6)
                   sin
                   0 4pi 0.2))

#<spline 19135>

(spline
  (function-points (xy 0 3)
                   (lambda (x)
                     (- (expt (/ x 10) 3)))
                   0 4pi 0.5))

#<spline 19136>

(spline
  (function-points (xy 0 0)
                   (lambda (x)
                     (damped-oscillatory 1.5 0.4 4 x))
                   0 4pi 0.05))

#<spline 19137>

which create the curves shown in this figure.

Curve generated by the function function-points. From top to bottom, we have a sinusoid, an inverted exponential and a damped sinusoid.

Even though the generalization of higher-order functions allows these to be used in a wide variety of situations, sometimes, it is preferable to encapsulate a particular use in a function more easily recognizable. For example, if a program is systematically computing points of a sinusoid, then it is likely that this program becomes more readable if in fact the sinusoid-points function exists. But even in this case, the order functions will allow this function to be defined in a simpler way. That way, instead of having to write the usual recursive definition:

(define (sinusoid-points p a omega fi x0 x1 dx)
  (if (> x0 x1)
    (list)
    (cons (+xy p x0 (sinusoid a omega fi x0))
          (sinusoid-points p a omega fi (+ x0 dx) x1 dx))))

we can now write:

(define (sinusoid-points p a omega fi x0 x1 dx)
  (function-points
    p
    (lambda (x) (sinusoid a omega fi x))
    x0 x1 dx))
9.4.1 Exercises 40
9.4.1.1 Question 190

Consider the balconies shown on the following image:

The balconies are composed of a slab and a guardrail, the guardrail being composed by the uprights and the handrail. Define a function called balcony which, conveniently parametrized, is capable of generating not only the balconies shown in the previous image but also many others. For that, the balcony function should receive not only the geometric parameters of the balcony (as is the slab’s thickness or the height of the handrail, etc.), but also the function which determines the outside curve of the balcony.

9.4.1.2 Question 191

Define the necessary functions and write the Racket expressions that reproduce the balconies shown in the previous image.

9.4.1.3 Question 192

Consider the following image where we present a sequence of cylindrical tubes joined by spheres that make up a random path in which the path sections are parallel to the coordinate axes. Note that the tubes have a random length between a maximum length and \(10\%\) of that maximum length and that the changes in direction are also random but never the reverse of the immediately previous direction. Also note that the tubes have a radius that is \(2\%\) of the maximum length of the tube and that the spheres have a radius that is \(4\%\) of the maximum length of the tube.

Define the function path-of-tubes that, given the point and the initial direction, the maximum length of tube and the number of tubes, creates a sequence of tubes joined by spheres that follow a random path.

9.4.1.4 Question 193

Even though the path can be random, it is possible to limit it in space so as to never exceed a certain region. This limitation is visible in the following image where, from left to right, we can see a sequence of tubes following a random path limited by a cube, a random path limited by a cylinder, and finally, a random path limited to a sphere.

Define the function tubes-shape as a generalization of the function path-of-tubes in order to receive, in addition to the parameters of the latter, an additional parameter that should be a predicate which, given a hypothetical point to the path extension, indicates whether this point is contained in the region in question. If it is not, the program should reject this point and generate a new one.

Also define the functions cube-of-tubes, cylinder-of-tubes and sphere-of-tubes, which have the same parameters of the function path-of-tubes and that use the function path-of-tubes with the appropriate predicate to the desired shape.

9.5 Identity Function

We saw that the summation function implements the classic summation used in algebra \(\sum_{i=a}^bf(i)\):

(define (summation f a b)
  (if (> a b)
    0
    (+ (f a)
       (summation f (+ a 1) b))))

All summations can be calculated simply by specifying, on the one hand, the function \(f\) which provides each term of the sum and, on the other, the \(a\) and \(b\) limits of that summation. For example, the mathematical expression \(\sum_{i=1}^{10}\sqrt{i}\) is calculated by the corresponding Racket expression:

(sum sqrt 1 10)

In case the summation terms are calculated by an expression for which Racket does not have a pre-defined function, the more immediate solution will be to use a lambda expression. For example, to calculate \[\sum_{i=1}^{10}\frac{\sin i}{\sqrt{i}}\]

we can write:

(summation (lambda (i) (/ (sin i) (sqrt i))) 1 10)

In general, the specification of the function \(f\), which computes each term of the summation, does not raise any difficulties but there is a particular case which may cause some perplexity and therefore deserves being discussed. Let us consider the \(\sum_{i=1}^{10}i\) summation. Which is the Racket expression that calculates it?

In this latter example, it is not entirely clear what is the function \(f\) that is at stake in the calculation of the summation, because the observation of the mathematical expression correspondent to the term of the summation does not allow the uncovering of any function. And yet, it is there. To make it clearer we have to remember that the summation function calculates \(\sum_{i=a}^bf(i)\), whereby, in this latter example, the parameters in question will have to be \(a=1\), \(b=10\) and, finally, \(f(i)=i\). It is this last function that is relatively strange: given an argument, it merely returns this argument, without performing any operation on it. Mathematically speaking, this function is called identity function and corresponds to the neutral element of the composition of functions. The identity function can be trivially defined in Racket based on its mathematical definition:

(define (identity x) x)

Although it seems to be a useless function, the identity function function is in reality, very useful. First of all, because it allows us to calculate the \(\sum_{i=1}^{10}i\) summation just by writing:

> (sum identity 1 10)
55

Many other problems also benefit from the identity function. For example, if we define the function that computes the product \(\prod\):

\[\prod_{i=a}^{b}f(i)=f(a)\cdot f(a+1)\cdot\cdots\cdot f(b-1)\cdot f(b)\]

(define (product f a b)
  (if (> a b)
    1
    (* (f a)
       (product f (+ a 1) b))))

it becomes trivial to define the factorial function by the product: \[n!=\prod_{i=1}^{n}i\]

(define (factorial n)
  (product identity 1 n))

Further ahead we will find new uses for the identity function.

9.5.1 Exercises 41
9.5.1.1 Question 194

Both the summation and the product can be seen as special cases of another even more generic abstraction, designated accumulation. In this abstraction, the parameters are: the operation of combination of elements, the function to apply to each one, the initial value, the lower limit, the transition to the next element (designated as successor) and the upper limit. Define this function. Also define the summation and the product in terms of accumulation.

9.5.1.2 Question 195

It is known that the sum \(\frac{8}{1\cdot 3}+\frac{8}{5\cdot7}+\frac{8}{9\cdot11}+\cdots\) converges (very slowly) to \(\pi\). Using the accumulation function defined in previous exercise, define the function that calculates an approximation of \(\pi\) to the \(n\)-th term of the sum. Determine an approximation of \(\pi\) to the term \(2000\).

9.5.1.3 Question 196

The enumerate function is capable of generating sequences in arithmetic progression, i.e. sequences in which there is a constant difference between each two elements. For example,

> (enumerate 0 20 2)

'(0 2 4 6 8 10 12 14 16 18 20)

It may be necessary, however, to produce sequences in which the elements develop differently, for example, in geometric progression.

Define the higher-order function succession that receives the limits \(a\) and \(b\) of a range and also the function \(f\) and generates a list with all the elements \(x_i\) that do not exceed \(b\) such that \(x_0=a\) and \(x_{i+i}=f(x_i)\). For example:

> (succession 2 600 (lambda (x) (* x 2)))
(2 4 8 16 32 64 128 256 512)
9.5.1.4 Question 197

Redefine the enumerate function in terms of the succession function.

9.6 The Function Restriction

In previous we saw several examples of High-Order functions that received functions as arguments. In this section we will look at High-Order functions that produce other functions as a result.

Let us start by considering a function that computes the double of a given number:

(define (double x)
  (* 2 x))

We can immediately see that the function double represents a particular case of multiplication between two numbers. More specifically we say that the function double is a restriction of the function * where the first operand is always 2. The same can be said for the functions that calculate the triple or quadruple of a number.

As a second example let us consider the exponential function e^x, where \(e^x = 2.718281828459045\) is the base of Neperian logarithm. This function can be defined in terms of the Exponentiation function a^b:

(define (exponential x)
  (expt 2.718281828459045 x))

As we can see the function exponential uses the function expt systematically with the same operand. Once more, it is a restriction of a function that has a fixed operand.

This type of restriction, where an operation is used between two operands and always with the same first operand, is a pattern that can be easily implemented using a High-Order function. To better understand the definition of this function let us first write the two previous functions using anonymous functions. As we saw in section 0.4 every definition of a function implies the creation of an anonymous function that is then associated to a given name. We have:

(define double
  (lambda (x)
        (* 2 x)))
(define exponential
  (lambda (x)
        (expt 2.718281828459045 x)))

By comparing these two anonymous functions we can tell that the only difference between them is simply the function used (* in on case and \(e^x = 2.718281828459045\) in the other) and the first operand (\(2\) in one case and \(2.718281828459045\) in the other). This suggests that we can define a function that receives these two differences as parameters and produces the corresponding anonymous function:

(define (restriction f a)
  (lambda (x)
    (f a x)))

Using this function we can now write:

(define double (restriction * 2))
(define exponential (restriction expt 2.718281828459045))

and use them as any other function:

> (double 3)
6
> (exponential 2)
7.3890560989306495

The most interesting aspect of the function restriction is that it produces a function as a result. That allows it to be used not only to define other functions but to let us write more simple expressions as well. For example, in Mathematics we define the n-th harmonic number with the formula:

\(H_n=1+\frac{1}{2}+\frac{1}{3}+\cdots\frac{1}{n}={\sum_{k=1}^{n}\frac{1}{k}}\)

Using the function restriction we can define the previous function in Racket:

(define (harmonic-number n)
  (summation (restriction / 1) 1 n))

Naturally, if the expression (restriction / 1) is to be repeatedly used then it is convenient to give it a name:

(define inverse (restriction / 1))

so that we can simply write:

(define (harmonic-number n)
  (summation inverse 1 n))

The function restriction is useful enough for it to be pre-defined in Racket under the name curry, in honour of Mathematician Haskell Curry.

Haskell Curry was a 20th century American Mathematician who made important contributions in the field of Combinatory Logic, collaborated in developing one of the first computers and invented one of the first programming languages.

9.6.1 Exercises 42
9.6.1.1 Question 198

Similar to what we did to the functions double,exponential, and inverse, use the function restriction (or curry) to define the functions successor and symmetrical capable of calculating the successor of a number and the symmetrical value of a number, respectively.

9.6.1.2 Question 199

The function curry is also useful for defining predicates. Using this function define the predicate negative?, that returns true for all negative numbers, and the predicate one?, that returns true only for the number 1.

Even though the function curry allows for simplifying the creation of restrictions of functions, it only solves half the problem. For us to see the other half of the problem we need only think of functions such as the square of a number or the predecessor of a number. Their definitions are:

(define (square-number x) (expt x 2))
(define (predecessor n) (- n 1))

In these two examples we notice that they too are restrictions of other existing functions but this time with a fixed second operand. This prevents the use of curry since it produces functions with a fixed first operand, but nothing prevents us from defining an alternative form of restriction that applies to the second operand. In fact, this second alternative of restriction is already pre-defined in Racket under the name curryr, where r means right to indicate the function fixes the operand on the right. Using this High-Order function we get:

(define square-number (curryr expt 2))
(define predecessor (curryr - 1))
9.6.2 Exercises 43
9.6.2.1 Question 200

Define the function curryr.

9.7 The Composition Function

One of the most useful High-Order functions is the one which allows the composition of functions. Given two functions f and g, the composition of f with g is written \((f \circ g\)\) and is defined by the function:

\((f \circ g)(t)=f(g(t))\)

This way of defining the composition of functions shows that the function f is to be applied to the result of g but what it does not show is that it is in fact defining the function \(\circ\). To make this definition more apparent it is useful to use the much more formal notation \(\circ(f,g)\). In this form it becomes much more evident that \(\circ\) is a function that takes other functions as arguments. With just that we can state that \(\circ\) is a high order function but it actually does more than just receiving two functions as arguments, it also produces a new function as a result that, for a given parameter t, calculates \(f(g(t))\). Seeing as this new function has no name the best way of producing it is with a lambda expression. The correct definition of the combination function is:

\(\circ(f,g)=\lambda tf(g(t))\)

We can now define it in Racket:

(define (composition f g)
  (lambda (t)
    (f (g t))))

Using the function composition we can now create other functions more easily. For example, we know that the number \(e=2.718281828459045\), the base of Neperian numbers, can be defined in terms of the function summation:

\(\sum_{n=0}^{\infty}\frac{1}{n!}\)

Since the summation function uses the composition of the functions inverse and factorial we can get close value of e by computing only a few terms of the summation:

> (summation (composition inverse factorial) 0.0 20.0)

6613313319248080001/2432902008176640000

Just as we did in the previous section it might also be useful here to not only create but also to define functions in terms of the composition of other functions. For example, if we wish to have a function that gives us the second element of a list we know that we must first use the function cdr, to obtain the list without the first element, and then the function car, to get the first element of the resulting list. This is nothing less than the composition of the function car and cdr:

(define second-element (composition car cdr))

Since the result of composition is a function, the name second-element designates that function and should be used as such:

> (second-element '(a b c))

'b

Likewise, if we wish to define a function that gives us the third element of a list we can write:

(define third-element (composition car (composition cdr cdr)))

Obviously for arbitrary combinations of functions it might me useful to use a more compact way of writing them. For that we can group the functions we wish to compose in a list, and write:

(define fourth-element (compositions (list car cdr cdr cdr)))

Since we need to process a list of functions we can define the function compositions using recursion, where we make the composition of the first element of the list with the result of composing the remaining elements- Here is a first sketch of this function:

(define (compositions fs)
  (if (null? fs)
      ???
      (composition (car fs)
                   (compositions (cdr fs)))))

What we need to find out is what should come in place of ???. If we collapse the computational process for (compositions (list f g h)), we have:

(composition f (composition g (composition h ???)))

It is logical to thing that instead of (composition (emph "h") ???) we should have just h which means that instead of ??? we should have the neutral element of the composition of functions. That element is none other than the identity function, for which we will now have:

(define (compositions fs)
  (if (null? fs)
    identity
    (composition (car fs)
                 (compositions (cdr fs)))))

Given the usefulness of the composition function it is already pre-defined in Racket under the name of compose, which takes any number of arguments and returns as result their composition.

9.7.1 Exercises 44
9.7.1.1 Question 201

Define the function power-of-four in terms of the functions power-of-two and compose.

9.7.1.2 Question 202

Define the function identity in terms of the function compose.

9.7.1.3 Question 203

Using the functions curry and compose, define the function symmetrical-function that, given the function \(f(x)\) produces the symmetrical function \(-f(x)\). Its evaluation must produce the following interaction:

> (define -sqrt (symmetrical-function sqrt))
> (-sqrt 2)

-1.4142135623730951

9.8 Higher Order Functions on Lists

We have seen, upon the introduction of lists as recursive data structure, that the processing of lists was easily accomplished through the definition of recursive functions. In fact, the behaviour of these functions was quite stereotypical: the functions began by testing whether the list was empty, and if not, the first element of the list would be processed, processing the remaining by a recursive invocation.

As we have seen in the previous section, when some functions have a similar behaviour, it is advantageous to abstract them in a higher-order function. That is precisely what we are going to do now by defining higher-order functions for three common cases of list processing: mapping, filtering and the reduction.

9.8.1 Mapping

One of the most useful operations is the one that transforms a list into another list by applying a function to each element of the first list. For example, given a list of numbers, we might be interested in producing another list containing the squares of these numbers. In this case we say that we are mapping the square function onto a list to produce the list of squares. The function definition presents the typical pattern of recursion on lists:

(define (mapping-square lst)
  (if (null lst)
    (list)
    (cons (sqr (car lst))
          (mapping-square (cdr lst)))))

Obviously, defining a function to only map the square is to particularize in excess. It would be much more useful to define a higher-order function that maps any function on a list. Luckily, it is trivial to modify the previous function:

(define (mapping f lst)
  (if (null? lst)
    (list)
    (cons (f (car lst))
          (mapping f (cdr lst)))))

With this function it is trivial to produce, for example, the square of all the numbers from \(10\) to \(20\):

> (mapping sqr (enumerate 10 20 1))

'(100 121 144 169 196 225 256 289 324 361 400)

The function mapping already exists pre-defined in Racket with the name map. The implementation provided in Racket also allows mapping over multiple lists simultaneously. For example, if we want to sum the elements of two lists two by two, we can write:

> (map + (enumerate 1 5 1) (enumerate 2 6 1))

'(3 5 7 9 11)

In addition to this function, Racket also provides a syntactic variant that can be useful to avoid writing anonymous functions: the form for/list. Using this form, the above calculation can be carried out by:

> (for/list ((x (enumerate 1 5 1))
             (y (enumerate 2 6 1)))
       (+ x y))

'(3 5 7 9 11)

9.8.2 Filtering

Another useful function is the one that filters a list. The filtering is performed by providing a predicate that is applied to each element of the list. The elements which satisfy the predicate (i.e., for which the predicate is true) are collected in a new list.

The definition is simple:

(define (filtering p lst)
  (cond ((null? lst)
         (list))
        ((p (car lst))
         (cons (car lst)
               (filtering p (cdr lst))))
        (else
         (filtering p (cdr lst)))))

Using this function we can, for example, get only the squares emph{divisible by \(3\)} of the numbers between \(10\) and \(20\):

> (filtering (lambda (n) (= (remainder n 3) 0))
             (mapping sqr (enumerate 10 20 1)))

'(144 225 324)

The function filtering already exists pre-defined in Racket with the name filter.

9.8.3 Reduction

A third function that is very useful is the one that performs a reduction in a list. This higher-order function receives an operation, an initial element and a list and will reduce the list through the "interleaving" operation between all elements of the list. For example, to add all the elements of a list l\(=\)(e0 e1 ... en) we can do (reduce + 0 l) and obtain e0\(+\)e1\(+\)... \(+\)en\(+\)0. Thus, we have:

> (reduce + 0 (enumerate 1 100 1))

5050

The function definition is quite simple:

(define (reduce f v lst)
  (if (null? lst)
    v
    (f (car lst)
       (reduce f v (cdr lst)))))

To see a more interesting example of the use of this functions, let us consider calculating the factorial of a number. According to the, non-recursive, traditional definition we have:

\[n! = 1\times 2\times 3\times \cdots{} \times n\]

Differently put, it is the product of all the numbers of an enumeration from 1 to n, i.e.:

(define (factorial n)
  (reduce * 1 (enumerate 1 n 1)))

As we can confirm in the previous examples, the initial value used is the neutral element of the combination operation of the list elements, but it need not be necessarily so. To see an example where this does not happen, let us consider determining the highest value existing in a list of numbers. In this case, the function that we use to successively combine the values of the list is the max function, which returns the greatest of two numbers. If l is the list of numbers, (e0 e1 e2 ...), the greater of those numbers can be obtained by (max e0 (max e1 (max e2 ...))) which, obviously, corresponds to a list reduction using a max function. We are left with determining the initial element to be used. One hypothesis is to use the negative infinity \(-\infty\) since any number will be greater than it. Another, more simple, will be to use any of the elements of the list, particularly the first as it is the one with easiest access. Thus, we can define:

(define (max-list lst)
  (reduce max (car lst) (cdr lst)))

The function reduce already exists pre-defined in Racket with the name foldr (foldr f i '(e0 e1 e2 ... en)) calculates (f e0 (f e1 (... (f en i))))

There is another similar function called foldl that performs the combination of elements in another order: (foldl f i '(e0 e1 e2 ... en)) calculates (f (... (f (f e0 i) e1) ...) en)

This order of difference allows the function foldl to compute the result in a more efficient manner than is possible with the foldr function. However, one must keep into account that applying the the function foldl is only equivalent to the function foldr when the combination function f is commutative and associative.

9.8.4 Exercises 45
9.8.4.1 Question 204

Consider a surface represented by a set of three-dimensional coordinates, as presented in the following figure:

Assume that these coordinates are stored in a list of lists in the form:

Define the function surface-interpolation that receives a list with the previous form, starts by creating a list of splines in which each spline \(S_i\) passes through the points \((p_{i,0}, p_{i,1},\ldots,p_{i,5})\) as shown in the following image on the left and ends up using this list of splines \(S_0,S_1,\ldots,S_5\) to make a smooth interpolation of sections, as shown in following image on the right:

9.9 Generation of Three-Dimensional Models

Until now, we have used the CAD tool just as a visualizer of the forms that our programs generate. We will now see that a CAD tool is not only a drawing program. It is also a database of geometric figures. In fact, every time we draw something the CAD tool records the created graphic entity in its database, as well as some additional information related to that entity, for example its colour.

There are several ways to access the created entities. One of the simplest ways for a "normal" CAD tool user will be by using the mouse, simply by "clicking" on the graphic entity to which we want to access. Another way, more useful for those who want to program, will be by calling Racket’s functions that return, as results, the existing geometrical entities. There are several of those functions at our disposal but, for now, let us confine ourselves to one of the simplest: the function all-shapes

The all-shapes function does not receive any arguments and returns a list of all existing geometric entities in the CAD tool. Naturally, if there are no entities, the function returns an empty list.

The following interaction demonstrates the behaviour of this function:

> (all-shapes)

'()

> (circle (xy 1 2) 3)

#<circle 0>

> (sphere (xyz 1 2 3) 4)

#<sphere 1>

> (all-shapes)

'(#<circle 2> #<solid 3>)

In the previous interaction we notice that the function all-shapes creates representations of the geometric entities existent in the CAD tool without having any idea of how these entities got there, which prevents it from relating the existing forms, such as #<circle 2>, with forms that were created from Rosetta, such as #<circle 0>. Furthermore, the CAD tool does not always provide information about the type of geometrical form in question, which explains the fact that the sphere #<sphere 1> created by Rosetta will subsequently be recognized just as the solid #<solid 3>.

When necessary (and possible), these forms can be identified by the recognizers point?, circle?, line?, closed-line?, spline?, closed-spline?, surface?, and solid?.

Given a geometric entity, we may be interested in knowing its properties. The access to these properties depends much on what the CAD tool is able to provide. For example, for a circle, we can know its centre through the function circle-center and its radius through the function circle-radius, while for a point the function point-position returns its position and for a polygonal line, the function line-vertices produces a list with the positions of the vertices of that line. But for a generic solid, we have no access to any property.

In summary, we have:

(point-position (point p)) = p

(circle-center (circle p r)) = p

(circle-radius (circle p r)) = r

(line-vertices (line p0 p1 ... pn)) \(=\) '(p0 p1 ... pn)

While the above equivalences show the relations between the constructors of geometric entities (point, circle,etc.) and the selectors of these entities (point-position,circle-center, etc.) it is important to take into account that it is possible to use the selectors with entities that were not created from Rosetta, but directly in the CAD tool. This can be particularly useful when we want to write programs that, instead of generating geometry, only process existing geometry.

We will now exemplify the use of these operations in solving a real problem: the creation of three-dimensional models of buildings from two-dimensional plans provided by municipal services. These plans are characterized by representing each building with a polygon that delimits it, containing, in its interior, a point that indicates the building’s height. This figure shows a fragment of one of these plans, where a large enough icon was used for the points in order to make them more visible.

Plan supplied by town hall services. Each building is represented by its surrounding polygon and a point at the building’s height. Due to errors in plans, some buildings may not have their respective height while others may have more than one point. There may also be heights that are not associated with any building.

Unfortunately, it is not uncommon to find plans with errors and the example shown in this figure illustrates precisely this: a careful observation will reveal buildings that do not possess any height point, as well as height points that are not associated with any building. Furthermore, it is possible to find buildings with more than one height point. These are problems that we have to take into account when we think about creating programs that manipulate these plans.

In order to create a three-dimensional model from one of these plans we can, for each polygon, create a region that we will extrude up to the height indicated by the corresponding point. To do so, we have to be able to identify, for each polygon, which point that is (or what points those are in case of there being more than one).

Detecting if a point is contained within a polygon is a classic problem of Computational Geometry for which there are various solutions. One of the simplest is to draw a ray from this point and count the number of intersections that this ray has with the edges of the polygon, as illustrated in this figure with several points. If the number of intersections is zero, then the point is of course outside the polygon. If the number is one, then the point is necessarily inside the polygon. If this number is two, then it is because the radius entered and exited the polygon and, therefore, the point is outside the polygon; if the number is three, then it is because the radius exited, entered, and exited back out of the polygon and, therefore, the point is inside the polygon. Since each radius entry in the polygon implies its exit, it becomes evident that if the number of intersections of the radius with the polygon is even, then it is because the point was outside the polygon and if the number of intersections is odd then it is because the point was inside the polygon.

The use of a ray to determine if a point is contained within a polygon.

The implementation of this algorithm is relatively trivial: given a point \(P=(P_x,P_y)\) and a list \(V_s\) with the vertices of the polygon \(V_0,V_1,\ldots{},V_n\), we "create" a ray with its origin in \(P\) and of which the destination is a point \(Q=(Q_x,Q_y)\) sufficiently away from the polygon. To simplify, we will arbitrate that this ray is horizontal, which allows the destination point \(Q\) to have the same ordinate \(P\), i.e., \(Q_y=P_y\). To ensure that \(Q\) is sufficiently far enough from the polygon, we can calculate the largest abscissa of the vertices of the polygon \(\max({V_0}_x,{V_1}_x,\ldots,{V_n}_x)\) and we add a small distance, for example, one unit.

The calculation of the vertices greater abscissa is trivial to perform when using higher-order functions: we can start by mapping the list of vertices onto the list of its abscissa, and then, we operate a reduction of this list with the max function. This reasoning can be translated into Racket by the following function:

(define (point-in-polygon? p vs)
  (let ((q (xy (+ (foldl max (cx (car vs)) (map cx vs)) 1)
               (cy p))))
  ...))

Next, we determine the intersections between the segment defined by points \(P-Q\) and the segments that make up the polygon. Those segments are defined by the vertices \(V_0-V_1\), \(V_1-V_2\), ..., \(V_{n-1}-V_n\) and \(V_n-V_0\), that we can get through a mapping along two lists, one with the points \(V_0,V_1,\ldots{},V_n\) and the other with the points \(V_1,\ldots{},V_n,V_0\). Our function takes then the form:

(define (point-in-polygon? p vs)
  (let ((q (xy (+ (foldl max (cx (car vs)) (map cx vs)) 1)
               (cy p))))
  ...(map (lambda (vi vj)
            ...)
          vs
          (append (cdr vs) (list (car vs))))))

The function that we map along the two lists of vertices should produce, for each two consecutive vertices \(V_i\) and \(V_j\) the intersection of the segment \(P-Q\) with the segment \(V_i-V_j\).

To determine the intersection of two line segments, we can think as follows: given the points \(P_0\) and \(P_1\) that delimit a line, every point \(P_u\) between \(P_0\) and \(P_1\) is determined by the equation \[P_u = P_0 + u(P_1 - P_0), 0\leq u\leq 1\]

If this line segment intersects another line segment delimited by points \(P_2\) and \(P_3\) and defined by \[P_v = P_2 + v(P_3 - P_2), 0\leq v\leq 1\] then it is clear that exists a \(u\) and \(v\) such as \[P_0 + u(P_1 - P_0) = P_2 + v(P_3 - P_2)\].

In the two-dimensional case, we have that \(P_i=(x_i,y_i)\) and the previous equation unfolds in the two equations:

\(x_0 + u(x_1 - x_0) = x_2 + v(x_3 - x_2)\) \(y_0 + u(y_1 - y_0) = y_2 + v(y_3 - y_2)\)

Solving the system, we obtain

\[u=\frac{(x_3-x_2)(y_0-y_2)-(y_3-y_2)(x_0-x_2)}{(y_3-y_2)(x_1-x_0)-(x_3-x_2)(y_1-y_0)}\] \[v=\frac{(x_1-x_0)(y_0-y_2)-(y_1-y_0)(x_0-x_2)}{(y_3-y_2)(x_1-x_0)-(x_3-x_2)(y_1-y_0)}\]

It is clear that for the divisions to be performed it is required that the denominator \((y_3-y_2)(x_1-x_0)-(x_3-x_2)(y_1-y_0)\) is different from zero. If that is not the case, it is because the lines are parallel. If they are not parallel, the case of the intersection being outside the line segments in question can occur, and so we have to check if the obtained values ​​\(u\) and \(v\) obey the conditions \(0\leq u\leq 1\) and \(0\leq v\leq 1\). When this happens, the exact point of intersection can be calculated by replacing \(u\) in the equation \(P_u = P_0 + u(P_1 - P_0)\).

This leads us to the following definition for a function that calculates the intersection:

(define (intersection-segments p0 p1 p2 p3)
  (let ((x0 (cx p0)) (x1 (cx p1)) (x2 (cx p2)) (x3 (cx p3))
        (y0 (cy p0)) (y1 (cy p1)) (y2 (cy p2)) (y3 (cy p3)))
    (let ((denominator (- (* (- y3 y2) (- x1 x0))
                          (* (- x3 x2) (- y1 y0)))))
      (and (not (zero? denominator))
           (let ((u (/ (- (* (- x3 x2) (- y0 y2))
                          (* (- y3 y2) (- x0 x2)))
                       denominator))
                 (v (/ (- (* (- x1 x0) (- y0 y2))
                          (* (- y1 y0) (- x0 x2)))
                       denominator)))
             (and (<= 0 u 1)
                  (<= 0 v 1)
                  (xy (+ x0 (* u (- x1 x0)))
                      (+ y0 (* u (- y1 y0))))))))))

Using this function, we can determine all intersections with the edges of a polygon by doing

(define (point-in-polygon? p vs)
  (let ((q (xy (+ (foldl max (cx (car vs)) (map cx vs)) 1)
               (cy p))))
  ...(map (lambda (vi vj)
            (intersection-segments p q vi vj))
          vs
          (append (cdr vs) (list (car vs))))))

The mapping result will be, for each pair of consecutive vertices \(V_i-V_j\), an intersection point with the line \(P-Q\) or #f in the case where the intersection does not exist. Since we only want to know the intersections, we can now filter this list, keeping only those which are points, i.e., those which are not false:

(define (point-in-polygon? p vs)
  (let ((q (xy (+ (foldl max (cx (car vs)) (map cx vs)) 1)
               (cy p))))
  ...(filter
       (lambda (e) e)
       (map (lambda (vi vj)
              (intersection-segments p q vi vj))
            vs
            (append (cdr vs) (list (car vs)))))))

Now, to know how many intersections occur, we need only measure the length of the resulting list and check if the result is odd:

(define (point-in-polygon? p vs)
  (let ((q (xy (+ (foldl max (cx (car vs)) (map cx vs)) 1)
               (cy p))))
    (odd?
      (length
        (filter
          (lambda (e) e)
          (map (lambda (vi vj)
                 (intersection-segments p q vi vj))
               vs
               (append (cdr vs) (list (car vs)))))))))
9.9.1 Exercises 46
9.9.1.1 Question 205

The function point-in-polygon? is not as efficient as it could be, since it performs a mapping followed by filtering only to count how many elements result in the final list. In practice, this combination of operations is no more than the counting of the number of times that a binary predicate is satisfied along successive elements of two lists. Define the operation how-many? that implements this process. For example, consider:

> (how-many? > '(1 5 3 4 6 2) '(1 6 2 5 4 3))

2

9.9.1.2 Question 206

In reality, the function how-many? already exists in Racket with the name count. Re-implement the function point-in-polygon? so that it uses the function count.

With the function point-in-polygon? it is now possible to establish the association between each point and each polygon as these occur in the plans provided by the municipal services. However, as we mentioned initially, we need to be careful with the fact that it is also possible, for a given polygon, that there is no associated point or that more than one associated point may exist. One way of treating all these situations identically will be to compute, for each polygon, the list of points it contains. Ideally, this list should have only one element, but in the case of there being errors in a plan, it could have none or have more than one. In either case, the function always returns a list, so there will never be an error.

To produce this list of points for a given polygon we must test each of the plan’s points to see if it belongs to the polygon. Now, this is nothing more than a filtering of a list of points, keeping only those that are contained in the polygon. Thus, admitting that pts is the plan’s list of points and vs are the vertices of a particular polygon, we can define:

(define (points-in-polygon pts vs)
  (filter (lambda (pt)
            (points-in-polygon?
              (xyz (cx pt) (cy pt) (cz (car vs)))
              vs))
          pts))

Finally, given a list of points representing the coordinates of the top of the buildings and a list of polygons (each implemented by the list of its vertices) representing the building’s base perimeter, we will create a three-dimensional representation of these buildings by determining, for each polygon, which points it contains and then act in accordance with the number of points found:

The correct treatment of these latter cases is relatively complex. As we only want to create a prismatic approximation to the shape of the building, we will employ a simple approach: we use as the building’s height the highest coordinate found. The following function implements this behaviour:

(define (creates-buildings points polygons)
  (for ((polygon polygons))
    (let ((pts (points-in-polygon points polygon)))
      (let ((n-pts (length pts)))
        (cond ((= n-pts 0))
 
              ((= n-pts 1)
               (creates-building
                 polygon
                 (cz (car pts))))
              (else
               (creates-building
                 polygon
                 (foldl max
                        (cz (car pts))
                        (map cz (cdr pts))))))))))
9.9.2 Exercises 47
9.9.2.1 Question 207

Another possible approach to calculate the height of a building corresponding to a polygon that contains a multiple number of points is to use the average of the \(z\) coordinates of these points. Implement this approach.

To create a building from the list of vertices of its perimeter and its height we will simply create a polygonal region at the base of the building that we then extrude to the top:

(define (creates-building vertices height)
  (if (> height 0)
    (extrusion (surface-polygon vertices)
               quota)
    #f))

Until now we have solved the problem only from a geometrical point of view, representing the plan’s points by its coordinates and the polygons by the coordinates of its vertices. However, as we know, what the CAD tool provides are geometrical entities and these are the ones that contain the information we need. Now, given a list of entities, selecting those that correspond to the points is nothing more than a filtering process that only keeps the entities that satisfy the predicate point?. Given these entities, obtaining their coordinates is nothing more than a mapping process with the function primary-point. This means that we can define the function that obtains the coordinates of all points existing in a list of entities.

(define (points entities)
  (map point-position (filter point? entities)))

Likewise, given a list of entities, we can select the list of polygons’ vertices through a filtering process followed by a mapping process, as follows:

(define (polygons entities)
  (map line-vertices
       (filter (lambda (ent)
                 (and (line? ent) (closed-line? ent)))
               entities)))

We should note that, in the previous definition, we are selecting both the closed polygonal lines and the opened ones. This is for dealing with the possibility of some polygonal lines being visually closed, even if they are not, from the point of view of the geometrical operation that was used to create them.

Finally, we are in conditions of defining a function which, from a plan’s set of entities, creates the corresponding three-dimensional representations:

(define (buildings<-entities entities)
  (creates-building (points entities) (polygons entities)))

Naturally, the set of entities to be processed can be selectively produced or, alternatively, they can be all the entities existing in a given plan. In the latter case, we only need to do:

(buildings<-entities (all-shapes))

all-shapes: undefined;

 cannot reference an identifier before its definition

As an example, we show in this figure the evaluation result of the previous expression for the plan presented in this figure.

Three-dimensional modelling of the buildings present in the plan shown in this figure.

10 Parametric Representation

10.1 Introduction

Until now, we have only produced curves described by functions in the form \(y=f(x)\). These functions are said to be in the Cartesian form.

One other form of mathematically representing a curve is by means of equations \(F(x,y)=0\). This form, called implicit, is more general than the former which, in fact, in nothing else than solving it in terms of the ordinate \(y\). As an example of a curve described in the implicit form let us consider the equation \(x ^ 2 + y ^ 2-r ^ 2 = 0\) which describes a circumference of radius \(r\) centred at \((0,0)\). Unfortunately, this second way of representing curves is not as useful as the previous, since it is not always trivial (or possible) to solve the equation in terms of the ordinate.

Except for lines, of which the general equation is \(ax + by + c = 0, b \ neq 0\) and where it is obvious that the resolution in terms of the ordinate is \(y = -\frac{ax+c}{b}\). For example, in the case of the circumference, the best we can do is produce two functions:

\[y(x)=\pm\sqrt{r^2-X^2}\]

There is, however, a third form for representing of curves that becomes particularly useful: the parametric form. The parametric form is based on the idea that the curve can be traced by a point of which the position evolves over time. The time is, here, merely a parameter that determines the position of the point on the curve. Resuming the case of the circumference centred at \((0,0)\), its parametric description will be

\[x(t)= r\cos t\]

\[y(t)= r\sin t\]

Obviously, for the coordinate point \((x, y)\) to trace the entire circumference, the parameter \(t\) needs only vary in the range \(0,2\pi\).

The previous equations are called the parametric equations of the curve. If, in a parametric representation, we eliminate the parameter, naturally we find the curve’s original equation. For example, in the case of the circumference, if we add the square of the parametric equations we obtain

\[x^2+y^2=r^2(\cos^2 t + \sin^2 t)=r^2\]

To see a practical example, let us consider the Archimedean spiral: the curve described by a point which moves with constant velocity \(v\) along a radius that rotates around a pole with constant angular velocity \(omega\). In polar coordinates, the equation of the Archimedean spiral has the form

\[\rho=\frac{v}{\omega}\phi\]

or, with \(alpha=\frac{v}{\omega}\),

\[\rho=\alpha\phi\]

When we convert the above equation to rectangular coordinates, using the formulas

\[\left\{ \begin{aligned} x&=\rho \cos \phi\\ y&=\rho \sin \phi \end{aligned}\right.\]

we get

\[\left\{ \begin{aligned} x(\phi)&=\alpha\phi \cos \phi\\ y(\phi)&=\alpha\phi \sin \phi \end{aligned}\right.\]

which, as can be seen, is a parametric description in terms of \(\phi\).

Simpler still is the direct conversion to the parametric form of the polar representation of the Archimedean spiral. We only have to replace \(phi\) with \(t\) and add one more equation that expresses this change, that is

\[\left\{ \begin{aligned} \rho(t)&=\alpha t\\ \phi(t)&= t \end{aligned}\right.\]

Although we explained the parametric representation in terms of bi-dimensional coordinates, the extension to three-dimensional space is trivial. For that, it is enough to consider each three-dimensional point a function of a parameter, i.e., \((x,y,z)(t)=(x(t),y(t),z(t))\).

10.2 Computation of Parametric Functions

Since the parametric representation significantly simplifies the creation of curves, we now intend to define functions that, from the parametric description of a curve, produce a list of points that interpolate that curve. To generate these points we will specify the limits of the interval of parameter \([t_0, t_1]\), and the progressive increment of its value \(\Delta_T\), to generate the sequence of values \(t_0, t_0+\Delta_t, t_0+2\Delta_t,\ldots,t_1\). We have seen that the function enumerate, defined in section Enumerations served precisely to generate a list of these values:

(define (enumerate t0 t1 dt)
  (if (> t0 t1)
    (list)
    (cons t0
          (enumerate (+ t0 dt) t1 dt))))

For example, if we have \(t_0=1\), \(t_1=5\), and \(\Delta_t=1\), we have:

>

#<procedure:>>

(enumerate 1 5 1)

'(1 2 3 4 5)

'(1 2 3 4 5)

'(1 2 3 4 5)

The computation of the coordinates of the Archimedean spiral can now be trivially accomplished by mapping the function that gives us the positions onto the list of values of \(t\):

(define (archimedean-spiral alpha t0 t1 dt)
  (map (lambda (t)
         (pol (* alpha t)
              t))
       (enumerate t0 t1 dt)))

In the previous definition, we see that the curve of the Archimedean spiral is positioned at the origin. If we want to make the centre of a spiral a parameter p, we need only operate a simple translation, i.e.:

(define (archimedean-spiral p alpha t0 t1 dt)
  (map (lambda (t)
         (+pol p
               (* alpha t)
               t))
       (enumerate t0 t1 dt)))

This figure shows three Archimedean spirals drawn from the following invocations:

(spline
  (archimedean-spiral (xy 0 0) 2.0 0 (* 4 pi) 0.1))

#<spline 19138>

(spline
  (archimedean-spiral (xy 50 0) 1.0 0 (* 6 pi) 0.1))

#<spline 19139>

(spline
  (archimedean-spiral (xy 100 0) 0.2 0 (* 36 pi) 0.1))

#<spline 19140>

Archimedean spirals. From left to right, the parameters are \(\alpha=2, t \in [0,4\pi]\), \(\alpha=1, t \in [0,6\pi]\), \(\alpha=0.2, t \in [0,36\pi]\).

10.3 Rounding errors

Although very useful, the enumerate function has a problem: when the increment dt is not an integer, its successive addition will accumulate rounding errors, which may cause a bizarre behaviour. To exemplify the situation, let us consider the number of elements of two enumerations of the interval \([0,1]\), the first with an increment of \(\frac{1}{10}\) and the second with an increment of \(\frac{1}{100}\):

> (length (enumerate 0 1 1/10))

11

> (length (enumerate 0 1 1/100))

101

Since the enumerate function produces a list that includes both the first and the last element of the interval, we conclude that the number of generated elements is correct. However, if we replace \(\frac{1}{10}\) and \(\frac{1}{100}\) by the equivalent \(0.1\) and \(0.01\) we obtain something strange:

> (length (enumerate 0 1 0.1))

11

> (length (enumerate 0 1 0.01))

100

Obviously, there is a problem there: apparently the second enumeration ended earlier than what would be expected, failing to produce the final element. Worse still, this situation seems to have an irregular behaviour, as we can see in the following interactions:

> (length (enumerate 0 1 0.001))

1000

> (length (enumerate 0 1 0.0001))

10001

> (length (enumerate 0 1 #i1e-05))

100001

> (length (enumerate 0 1 1e-06))

1000000

The problem arises from the fact that \(0.1\) and \(0.01\) and are not accurately representable in binary notation. In fact, \(0.1\) is represented by a number that is slightly smaller than \(\frac{1}{10}\), while \(0.01\) is represented by a number slightly greater than the fraction \(\frac{1}{100}\). The consequence is that a sufficiently large sum of these numbers eventually accumulates an error that makes the final calculated value, after all, greater than the limit, so it is discarded.

This phenomenon has been the cause of countless problems in the computer science world. From errors in anti-missile defence systems to the wrong calculation of stock market indexes, the history of computer science is filled with catastrophic examples caused by rounding errors.

To avoid these problems it would be natural to think that we should limit ourselves to using increments in the form of fractions but, unfortunately, that is not always possible. For example, when we use trigonometric functions (such as sines and cosines), it is usual to have to generate values in a range that corresponds to a multiple of the period of the functions that, as we know, involves the irrational number \(\pi\), not representable as a fraction. Thus, to deal with real numbers properly, we should use another approach based on producing the number of actually desired values, avoiding successive sums. For that, instead of using the increment as a parameter, we will use the number of desired values instead.

In its actual form, from the limits \(t_0\) and \(t_1\) and the increment \(\Delta_t\), the enumerate function generates each of the points \(t_i\) by calculating:

\[t_i=t_0+\underbrace{\Delta_t+\Delta_t+\cdots{}+\Delta_t}_{\text{$i$ vezes}} \equiv\] \[t_i=t_0+\sum_{j=0}^{i}\Delta_t\]

The problem of the previous formula is, as we have seen, the potentially large error accumulation that occurs when we add the small error that exists in the value of \(\Delta_t\) a large number of times. To minimize this error accumulation we have to find an alternative formula that avoids the term \(\Delta_t\). An attractive possibility is to consider, not an increment of \(\Delta_t\), but a \(n\) number of \(\Delta_t\) increments that we are interested in computing. Obviously, this \(n\) number relates to \(\Delta_t\) through the equation

\[n=\frac{t_1-t_0}{\Delta_T}\]

Consequently, we also have

\[\Delta_T=\frac{t_1-t_0}{n}\]

Replacing in the previous equation, we obtain

\[t_i=t_0+\sum_{j=0}^{i}\Delta_t \equiv\] \[t_i=t_0+\sum_{j=0}^{i}\frac{t_1-t_0}{n} \equiv\] \[t_i=t_0+\frac{t_1-t_0}{n}\sum_{j=0}^{i}1 \equiv\] \[t_i=t_0+\frac{t_1-t_0}{n}i\]

The fundamental aspect of the last equation is that it no longer corresponds to a sum of \(i\) terms where rounding error accumulation can occur, but to a "function" \(f(i)=\frac{t_1-t_0}{n}i\) of \(i\), where \(i\) causes no error since it is simply an integer that evolves from \(0\) to \(n\). From this number it is now possible to calculate the value of \(t_i\) that, although it may have the inevitable rounding errors caused by not using fraction numbers, it will not have an accumulation of these errors.

Based on this last formula it is now easy to write a new function range-n that directly computes the value of \(t_i\) from the number \(n\) of desired increments in the interval \([t_0, t_1]\):

(define (enumerate-n t0 t1 n)
  (map (lambda (i)
         (+ t0 (/ (* i (- t1 t0)) n)))
       (enumerate 0 n 1)))

Naturally, this new definition does not eliminate rounding errors, but it avoids their accumulation.

The enumerate-n function is actually pre-defined in Rosetta, and is called division. Similar to the function enumerate-n, the division function receives as parameters the interval limits and the number of increments:

> (division 0 1 4)

'(0 1/4 1/2 3/4 1)

> (division 0 pi 4)

'(0 0.7853981633974483 1.5707963267948966 2.356194490192345 3.141592653589793)

Different to the enumerate-n function, the division function also has an optional parameter (by omission, the value #t) intended to indicate whether we want to include the last element of the interval. This option is intended to facilitate the use of periodic functions. To better understand this parameter, let us consider that we want to place four objects on the four cardinal points. For this we can use polar coordinates, dividing the circle into four parts, with the angles \(0\), \(\frac{\pi}{2}\), \(\pi\), and \(\frac{3\pi}{2}\). However, if we use the expression (division 0 2pi 4) we will not only get those values but also the upper limit of the interval \(2\pi\), which would imply placing two overlapping objects, one for the angle \(0\) and another for \(2\pi\). Naturally, we can solve the problem by using (division 0 3pi/2 3) but that seems much less natural than to write (division 0 2pi 4 #f), that is, we want to divide \(2\pi\) in four pieces but do not want the last value because it will be equal to the first (to less than one period).

10.4 Mapping and Enumerations

As we saw in the archimedean-spiral function, generating parametric curves implies mapping a function over a division of an interval in equal parts. Because this combination is so frequent, Rosetta provides a function called map-division that performs it in a more efficient way. Formally, we have:

(map-division f t0 t1 n)\(\equiv\)(map f (division t0 t1 n))

The map, division, and map-division functions allow us to generate curves with great simplicity. They are, therefore, an excellent starting point for experimentation. In the following sections we will look at some of the curves that, by one reason or another, became part of the history of Mathematics.

10.4.1 Exercises 48
10.4.1.1 Question 208

Redefine the archimedean-spiral function that calculates a list of points through which an Archimedean spiral is traced, but using the division function. Instead of the increment \(\Delta_T\), your function must, instead, receive the number \(n\) of points.

10.4.1.2 Question 209

Define the archimedean-spiral-wall function that, from the thickness and height and also the parameters of an Archimedean spiral creates a spiral wall, as shown in the following image:

10.4.1.3 Question 210

Define the cylinders-archimedean-spiral function that creates \(n\) cylinders of radius \(r\) and height \(h\) and places them along an Archimedean spiral, in accordance with the parameters of this spiral, as presented in the following figure:

10.4.2 Fermat’s Spiral

Fermat’s spiral, also known as parabolic spiral, is a curve similar to an Archimedean spiral but in which the equation that defines it is

\[\rho^2=\alpha^2\phi\]

Solving the equation for \(\rho\), we get

\[\rho=\pm\alpha\sqrt{\phi}\]

Dividing the curve into two halves for handling the two signals, we have:

(define (half-fermat-spiral p a t n)
  (map (lambda (t)
         (+pol p
               (* a (sqrt t))
               t))
       (division 0 t n)))
(define (fermat-spiral p a t n)
  (append (reverse (half-fermat-spiral p (+ a) t n))
          (cdr (half-fermat-spiral p (- a) t n))))

To see an example of Fermat’s spiral, we can evaluate the following expression:

(spline (fermat-spiral (xy 0 0) 1.0 (* 16 pi) 400))

#<spline 19141>

The result of the previous evaluation is shown in this figure.

The Fermat’s spiral for \(\phi \in [0, 16\pi]\).

An interesting aspect of the Fermat’s spiral is that it models some natural phenomena, in particular the arrangement of seeds on a sunflower. On a sunflower, the seeds are arranged in circular surface and, in order to achieve growing the greatest number of seeds possible, they are leaning against each the other, in the most compact way possible.

In these flowers, each seed is positioned according to the equations

This model was proposed by H. Vogel in 1979 \cite{}.

\[\left\{ \begin{aligned} \rho &= \alpha \sqrt{n} \\ \phi &= \delta \times n \end{aligned}\right.\]

where \(n\) is the seed index counted from the centre of the flower

This index is inversely proportional to the seeds’ order of growth.

, \(\alpha\) is the scale factor and \(\delta\) is the golden angle (also called Fibonacci angle) and defined by \(\delta=\frac{\pi}{\Phi}^2\) where \(\Phi=\frac{\sqrt{5}+1}{2}\) is the golden ratio. From the definition results that \(\delta=2.39996\approx 2.4\).

We can see that this equation is identical to the Fermat’s spiral, but with the difference that the angle \(\phi\) does not grow in terms of \(n\), but instead in terms of \(\delta \times n\), moving every two seeds apart by an angle of \(\delta\).

To visualize this curve, it is preferable to draw a "seed" in each coordinate. Thus, we can define the function sunflower that, given a central point p, a growth factor a, an angle d, a radius r and a number of seeds n, draws the seeds of the sunflower with a circle of radius r for each seed.

(define (sunflower p a d r n)
  (for/list ((t (division 1 n (- n 1))))
    (circle (+pol p
                  (* a (sqrt t))
                  (* d t))
             r)))

The following evaluations allow us to draw an approximation to the arrangement of sunflower seeds:

(define golden-angle (/ 2pi (expt (/ (+ (sqrt 5) 1) 2) 2)))
 
(sunflower (xy 0 0) 1.0 golden-angle 0.75 200)

The result is visible in this figure.

The arrangement of Sunflower seeds.

It is interesting to note that the arrangement of seeds is extremely sensitive to the \(\delta\) angle. In this figure, the sunflowers are designed in all ways identical to this figure except in the \(\delta\) angle that differs from the golden angle \(\frac{\pi}{\Phi}^2\) by just a few hundredths of a radian.

The arrangement of imaginary sunflower seeds wherein, from left to right, the \(\delta\) angle corresponds to an increment, relative to the golden angle \(\frac{\pi}{\Phi}^2\) of \(+0.03\), \(+0.02\), \(+0.01\), \(+0.00\), \(-0.01\), \(-0.02\) e \(-0.03\).

10.4.3 Cissoid of Diocles

Legend has it that in the fifth century before Christ, the Greek population was hit by the plague. Hoping for a response, the Greeks resorted to the Oracle of Delos who prophesied that the problem was in the incorrect veneration that was being given to the God Apollo. According to the Oracle, Apollo would only be appeased if the volume of the cube-shaped altar, that the Greeks had dedicated to him, was doubled.

The Greeks, in an attempt to quickly get rid of the terrifying effects of the plague, immediately embarked on the task of building a new cubic altar, but, unfortunately, instead of duplicating its volume, they doubled its edge, implying that the final volume was eight times larger than the original volume, not just two times larger as the oracle had commanded. Apparently, the oracle knew what he was saying, because Apollo was not satisfied with the new altar and the plague continued to devastate Greece.

After realizing their mistake, the Greeks then tried to figure out what the correct change was that had to be made to the edge length of the cube to double its volume but never managed to find a solution. Doubling the volume of a cube then began to be called the problem of Delos and for centuries it tormented geometers.

Fortunately, the plague did not last as long as the geometers’ difficulties.

It was only two thousand years later, by the hand of Descartes, that it was demonstrated that the Greek techniques — that limited the geometric construction to the use of a ruler and a compass — could never have solved the problem.

However, long before Descartes, in the second century before Christ, the Greek mathematician Diocles had found a "solution" but at the expense of the use of a special curve, now called the Cissoid of Diocles. This extraordinary curve, unfortunately, can not be drawn just by using a ruler and compass, which implies that the solution found would be, at best, an approximate solution and not the true solution to the problem.

The Cissoid of Diocles is defined by the equation

\[y^2(2a-x)=x^3, x\in [0, a[\]

Placing \(y\) in terms of \(x\), we obtain:

\[y=\pm\sqrt{\frac{x^3}{2a-x}}\]

Unfortunately, this formulation of the curve is somewhat inappropriate because it forces us to separately handle the signals \(\pm\). A conversion to polar coordinates allows us to obtain

\[\rho^2\sin^2\phi(2a-\rho\cos\phi)=\rho^3\cos^3\phi\]

Simplifying and using the Pythagorean identity \(\sin^2\phi+\cos^2\phi=1\), we obtain

\[(1-\cos^2\phi)(2a-\rho\cos\phi)=\rho\cos^3\phi\]

that is,

\[2a(1-\cos^2\phi)-\rho\cos\phi+\rho\cos^3\phi=\rho\cos^3\phi\]

Simplifying, we finally obtain

\[\rho=2a(\frac{1}{\cos\phi}-\cos\phi)\]

Since that \(\cos\pm\frac{\pi}{2}=0\), the curve tends to infinity for those values. This delimits the range of variation to \(\phi \in ]-\frac{\pi}{2}, +\frac{\pi}{2}[\).

Obviously, to transform the polar representation into a parametric representation we simply do

\[\left\{ \begin{aligned} \rho(t)&=2a(\frac{1}{\cos t}-\cos t)\\ \phi(t)&= t \end{aligned}\right.\]

Finally, to enable "centring" the curve in an arbitrary point, we carry out a translation from that point. Thus, to define this curve in Racket we just have to do:

(define (cissoid-diocles p a t0 t1 n)
  (map-division
     (lambda (t)
       (+pol p
              (* 2 a (- (/ 1.0 (cos t)) (cos t)))
              t))
     t0
     t1
     n))

This figure shows a sequence of Cissoids of Diocles created from the expressions:

(spline (cissoid-diocles (xy  0 0) 10.0 -0.65 0.65 100))
(spline (cissoid-diocles (xy  5 0)  5.0  -0.8  0.8 100))
(spline (cissoid-diocles (xy 10 0)  2.5  -1.0  1.0 100))
(spline (cissoid-diocles (xy 15 0)  1.0 -1.25 1.25 100))
(spline (cissoid-diocles (xy 20 0)  0.5  -1.4  1.4 100))

Cissoids of Diocles. From left to right the parameters are \(a=10, t \in [-0.65,0.65]\), \(a=5, t \in [-0.8,0.8]\), \(a=2.5, t \in [-1,1]\), \(a=1, t \in [-1.25 1.25]\) e \(a=0.5, t \in [-1.4,1.4]\).

10.4.4 Lemniscate of Bernoulli

In 1694, Bernoulli published a curve to which he gave the name of lemniscate. That curve ended up becoming immensely famous for its adoption as the symbol to represent infinity: \(\Large \infty\). Nowadays, the curve is known as lemniscate of Bernoulli to distinguish it from other curves that have a similar shape.

The Lemniscate of Bernoulli is defined by the equation

\((x^2+y^2)^2=a^2(x^2-y^2)\)

Unfortunately, as we discussed earlier, this form of analytical representation of the curve is rather inappropriate for its drawing, as it is difficult to put one variable in terms of the other. However, if you apply the conversion from polar coordinates to rectangular coordinates we get

\[(\rho^2\cos^2 \phi + \rho^2\sin^2 \phi)^2=a^2(\rho^2\cos^2 \phi - \rho^2\sin^2 \phi)\]

Dividing both terms by \(\rho^2\) and using the trigonometric identities \(\sin^2 x + \cos^2 x=1\) and \(\cos^2 x - \sin^2 x= \cos 2 x\), we obtain

\(\rho^2=a^2\cos 2 \phi\)

This equation is now trivial to convert to the parametric form:

\[\left\{ \begin{aligned} \rho(t)&=\pm a \sqrt{\cos 2 t}\\ \phi(t)&= t \end{aligned}\right.\]

Note that the presence of \(\pm\) indicates that, in fact, two curves are being plotted simultaneously. To simplify, let us plot each of these curves independently. Thus, let us start by defining a function that calculates half the lemniscate with its origin at the point p:

(define (half-lemniscate-bernoulli p a t0 t1 n)
  (map-division
    (lambda (t)
      (+pol p
             (* a (sqrt (cos (* 2 t))))
             t))
    t0
    t1
    n))

Next, we define a function that draws a complete lemniscate from the drawing of two half lemniscates:

(define (graph-lemniscate-bernoulli p a t0 t1 n)
  (spline
   (append (half-lemniscate-bernoulli p (+ a) t0 t1 n)
           (cdr (half-lemniscate-bernoulli p (- a) t1 t0 n)))))

We can now visualize the "infinity" produced by this curve with the following expression:

(graph-lemniscate-bernoulli (xy 0 0) 1.0 -pi/4 pi/4 100)

The result is shown in this figure.

Lemniscate of Bernoulli.

10.4.5 Exercises 49
10.4.5.1 Question 211

Even though in the previous examples we have used the parametric representation, for it being the most simple, this is not always the case. For example, the lemniscate of Gerono studied by the mathematician Camille-Christophe Gerono, is mathematically defined by the equation

\[x^4 - x^2 + y^2 = 0\]

Solving for \(y\), we obtain

\[y=\pm\sqrt{x^2-x^4}\]

To remain on the real plane, it is necessary that \(x^2-x^4\ge 0\), which implies \(x \in [-1,1]\). To avoid having to draw two curves (caused by the presence of the symbol \(\pm\)), we could be tempted to use the parametric representation of this curve, defined by

\[\left\{ \begin{aligned} x(t)&=\frac{t^2-1}{t^2+1}\\ y(t)&=\frac{2t(t^2-1)}{(t^2+1)^2} \end{aligned}\right.\]

Unfortunately, while the Cartesian representation only needs to range \(x\) between \(-1\) and \(+1\), the parametric representation needs to range \(t\) from \(-\infty\) to \(+\infty\), which raises a computational impossibility. For this reason, in this case, the Cartesian representation is preferable.

Taking these considerations into account, define the function Graph-lemniscate-gerono that draws the lemniscate of Gerono.

10.4.6 Lamé Curve

The Lamé curve is a curve for which the points satisfy the equation

\[\left|\frac{x}{a}\right|^n\! + \left|\frac{y}{b}\right|^n\! = 1\]

wherein \(n>0\) and \(a\) and \(b\) are the curve radii.

The curve was studied in the nineteenth century by the French mathematician Gabriel Lamé as an obvious generalization of the ellipse. When \(n\) is a rational number, the Lamé curve is called a super-ellipse. When \(n>2\), we obtain a hyperellipse, the higher \(n\) the closer to a rectangle the curve is. When \(n<2\) we get a hypoellipse, the smaller the \(n\), the closer to a cross the curve is. For \(n=2\), we obtain an ellipse, and for \(n=1\), we get a lozenge. These variations are visible in this figure that shows variations of this curve for \(a=2\), \(b=1\) and different values ​​of \(n\).

The Lamé curve for \(a=2\), \(b=1\), \(n=p/q\), \(p,q\in {1..8}\). The variable \(p\) varies along the abscissas axis, while \(q\) varies along the ordinates axis.

The Lamé curve became famous when it was proposed by Danish scientist and poet Piet Hein as an aesthetic and functional compromise between forms based on rectangular patterns and forms based on curvilinear patterns. In his study for a street intersection in the Sergels Torg neighbourhood, in Stockholm, where there was hesitation between a traditional roundabout or a rectangular arrangement of roads, Piet Hein suggested a superellipse as the intermediate form between those two, producing the result that is visible in this figure. Of all the superellipses, the most aesthetically perfect were, in Piet Hein’s opinion, the ones parametrized by \(n=\frac{5}{2}\) and \(\frac{a}{b}=\frac{6}{5}\).

The superellipse proposed by Piet Hein for Sergels square, in Stockholm. Photography Nozzman.

image

To draw the Lamé curve it is preferable we use the following parametric formulation.

\[\left\{ \begin{aligned} x\left(t\right) &= a\left(\cos^2 t\right)^{\frac{1}{n}} \cdot \sgn \cos t\\ y\left(t\right) &= b\left(\sin^2 t\right)^{\frac{1}{n}} \cdot \sgn \sin t \end{aligned}\right. \qquad -\pi \le t < \pi\]

The translation of this formula to Racket is direct:

(define (super-ellipse p a b n t)
  (+xy p
        (* a (expt (expt (cos t) 2) (/ 1.0 n)) (sgn (cos t)))
        (* b (expt (expt (sin t) 2) (/ 1.0 n)) (sgn (sin t)))))

In order to generate a sequence of points of the superellipse we can, as previously, use the map-division function:

(define (superellipse-points p a b n pts)
  (for/list ((t (division -pi pi pts)))
    (super-ellipse p a b n t)))

Finally, to plot the superellipse, we can define:

(define (superellipse-curve p a b n pts)
  (spline
    (superellipse-points p a b n pts)))

The superellipses in this figure were generated using the previous function. For this, we defined a function test-superellipses which, for values of \(a\) and \(b\) and for a sequence of numbers, tests all the combinations of exponent \(n=p/q\), with \(p\) and \(q\) taking successive values from that sequence. For viewing the super-ellipses each of them has an origin proportional to the combination used of \(p\) and \(q\):

(define (test-superellipses a b ns)
  (for ((num ns))
    (for ((den ns))
      (superellipse-curve
        (xy (* num (* 2.5 a)) (* den (* 2.5 b)))
        a
        b
        (/ num den)
        100))))

This figure was directly generated from the evaluation of the following expression:

(test-superellipses 2.0 1.0 (list 1 2 3 4 5 6 7 8))
10.4.7 Exercises 50
10.4.7.1 Question 212

Define the function tank-superellipse that creates a tank with a superelliptical shape identical to the one of Sergels Torg’s square, as presented below:

Suggestion: define a function named curved-wall that builds walls along a given curve. The function should receive the thickness and height of the wall and an entity representing the curve (e.g., a circle, a spline, etc.). Consider using the function sweep defined in section Extrusions as well.

10.4.7.2 Question 213

Define the function circular-tanks that builds a succession of circular tanks of which the centres are located along a superellipse, as presented in the following image:

The function should receive the parameters of the superellipse, the parameters of a circular tank and the number of tanks to create.

10.4.7.3 Question 214

Define a function that, by invoking the functions tank-superellipse and circular-tanks, creates a square as similar as possible to Sergels Torg’s square, as shown in the following figure:

10.4.7.4 Question 215

Modify the previous function to produce a variant that is perfectly circular:

10.4.7.5 Question 216

Modify the previous function to produce the following variant:

10.4.7.6 Question 217

Modify the previous functions to produce the following variant:

10.5 Precision

Let us consider the butterfly curve, proposed by the mathematician Temple H. Fay. This periodic curve is defined by the following equation in polar coordinates:

\[r=e^{\sin \phi} - 2 \cos (4 \phi ) + \sin^5\left(\frac{2 \phi - \pi}{24}\right)\]

Similar to what we did before, we will consider the curve with respect to a starting point \(P\), within a range of variation of the independent parameter \(t \in [t_0, t_1]\).

(define (butterfly-curve p t0 t1 n)
  (map (lambda (t)
         (+pol p
               (+ (exp (sin t))
                  (* -2 (cos (* 4 t)))
                  (expt (sin (/ (- (* 2 t) pi) 24)) 5))
               t))
       (division t0 t1 n #f)))

To draw this curve we are left with determining the appropriate value of the parameter \(n\). For this, it is important to realize that the period of the Butterfly curve is \(24 \pi\), with the entire curve generated for \(t \in [0, 24\pi]\). Since this period is relatively large, the number \(n\) of points to be used should be enough so that the curve can be accurately reproduced and it is precisely here that the greatest difficulty arises: without knowing the shape of the curve, it is difficult to estimate which is the best value for \(n\). If the value of \(n\) is too low, the curve will not be faithfully represented, in particular, in places where there are very accentuated curvatures; if the value is too high, the greater the need for computational resources. In practice, some experimentation is required to understand what best value is.

This need for experimentation relative to the value of number of points to be used is clearly evident in this figure which shows the Butterfly curves drawn for the following increasing values ​​of \(n\):

(closed-spline (butterfly-curve (xy  0 10) 0 (* 24 pi)   10))
(closed-spline (butterfly-curve (xy 10 10) 0 (* 24 pi)   20))
(closed-spline (butterfly-curve (xy 20 10) 0 (* 24 pi)   40))
(closed-spline (butterfly-curve (xy 30 10) 0 (* 24 pi)   80))
(closed-spline (butterfly-curve (xy 40 10) 0 (* 24 pi)  160))
(closed-spline (butterfly-curve (xy  0  0) 0 (* 24 pi)  320))
(closed-spline (butterfly-curve (xy 10  0) 0 (* 24 pi)  640))
(closed-spline (butterfly-curve (xy 20  0) 0 (* 24 pi) 1280))
(closed-spline (butterfly-curve (xy 30  0) 0 (* 24 pi) 2560))
(closed-spline (butterfly-curve (xy 40  0) 0 (* 24 pi) 5120))

The butterfly curve produced for increasing values of the number \(n\) of points.

As we can verify, when the number of points is insufficient, the produced curves can be grotesque distortions of reality. For this reason, we should take particular care in choosing the value most suitable for this parameter.

An interesting alternative is to give the computer the responsibility of adapting the number of points to the necessities of each curve.

10.5.1 Adaptive Sampling

The map-division function allows us to generate the curve of any function \(f(t)\) in the interval \([t_0, t_1]\) by applying the function \(f\) to a sequence of \(n\) increments of \(t\) equally spaced \(\{t_0,\ldots,t_1\}\). This sequence is named sampling sequence.

It is easy to see that the greater the number of the sampling sequence elements, the greater the accuracy of the produced curve will be. Unfortunately, the greater that accuracy, the greater the effort expended for computing the function values in all elements of the sequence. In practice, it is necessary we find a balance between accuracy and computational effort.

Unfortunately, there are several functions for which it is difficult or even impossible to find an appropriate number of sampling points. As an example, let us try to produce the graph of the function \(f(t)=(t,\frac{1}{2}sin(\frac{1}{t}))\) in the interval \([0.05,0.8]\). This function has a curve for a graph that oscillates with a frequency that is greater the closer it is to the origin. This figure shows a sequence of graphs of this function in which, from left to right, the number of sampling points was progressively increased.

Graph of the function \(f(x)=\frac{1}{2}sin(\frac{1}{x})\) in the interval \([0.05,0.8]\). From left to right the sampling points \(8,15,30\) and \(60\) were employed.

As we can see in the figure, with few sampling points the resulting curve is a grotesque approximation of the real curve and it is only when the number of sampling points becomes very high that some precision is acquired. It so happens that that precision is only relevant in parts of the curve where there are large variations. In other areas where the curve evolves "smoothly" more sampling points only imply greater processing time, without any significant advantage to the quality of the result. This suggests that we should adapt the number of sampling points along the curve according to its "smoothness": in areas where the curve varies more sharply we should employ more sampling points, but in areas where the variation is more linear, we can decrease the number of sampling points. This way, the number of sampling points adapts to shape of the curve.

Adaptive generation of points

For us to employ an adaptive sampling we need to define a criteria to classify the "smoothness" of a curve. As illustrated in this figure, we can start by admitting that we have two abscissae \(x_0\) and \(x_1\) that we use to calculate the points \(P_0\) and \(P_1\). To know if the curve behaves in a linear fashion between those two points (and thus can be approximated by a line segment), we shall calculate the average value \(x_m\) of that interval and the corresponding point \(P_m\) of the curve. If the function is in fact linear in that interval, then the points \(P_a\) and \(P_m\) are collinear. In practice, that will rarely happen, so we will have to use a concept of approximate collinearity, for which we can use any of the several possible criteria, such as:

Other criteria would be equally applicable, but for now, let us choose the first. Its translation into Racket is as follows:

(define (approximately-collinear? p0 pm p1 epsilon)
  (let ((a (distance p0 pm))
        (b (distance pm p1))
        (c (distance p1 p0)))
    (< (area-triangle a b c) epsilon)))
(define (area-triangle a b c)
  (let ((s (/ (+ a b c) 2.0)))
    (sqrt (* s (- s a) (- s b) (- s c)))))

The approximately-collinear? function implements the criteria that allows us to say that the line segments connecting the points p0, pm, and p1 are a good approximation to the curve’s behaviour between those points. When this criteria does not occur, we can split the interval into two sub-intervals, and analyse each of them separately, concatenating the results.

(define (map-division-adapt f t0 t1 epsilon)
  (define p0 (f t0))
  (define p1 (f t1))
  (define tm (/ (+ t0 t1) 2.0))
  (define pm (f tm))
  (if (approximately-collinear? p0 pm p1 epsilon)
    (list p0 pm p1)
    (append (map-division-adapt f t0 tm epsilon)
            (cdr (map-division-adapt f tm t1 epsilon)))))

This adaptive way of producing a sample of a curve allows results with greater precision even when employing a relatively small total number of sampling points. This figure shows a sequence of graphs identical to those shown in this figure, but now using an adaptive sampling scheme with approximately the same number of sampling points in each case. As we can see, the graphs are substantially more accurate, particularly in areas close to the origin, without this visibly diminishing the quality of the curve in areas where the change is smoother.

Graph of the function \(f(x)=\frac{1}{2}sin(\frac{1}{x})\) in the interval \([0.05,0.8]\). From left to right, the sequence of adaptive sampling has, respectively, \(7,15,29\) and \(57\) points.

10.5.2 Exercises 51
10.5.2.1 Question 218

The algorithm used in the map-division-adapt function can not properly deal with periodic functions in which we have \(f(t_0)=f(t_1)=f(\frac{t_0+t_1}{2})\). In this case, the degenerate triangle formed by these three points has zero area, which makes the algorithm terminate immediately. Modify the function so it receives an additional parameter \(\Delta_T\) that determines the biggest admissible interval between two consecutive values of \(t\).

10.5.2.2 Question 219

The map-division-adapt function is not as efficient as it could be, in particular, because it systematically repeats the application of the function \(f\) at the midpoint: \(f(t_m)\) is calculated to decide whether the recursion continues and, when it continues, it will have to be calculated again to allow calculating \(f(t_0)\) or \(f(t_1)\) on the following call. Redefine the map-division-adapt function in order to avoid repetitive calculations.

10.6 Parametric Surfaces

Throughout history, architecture has explored a huge variety of forms. As we have seen, some of these forms correspond to solids well known since antiquity, such as cubes, spheres and cones. More recently, architects have turned their attention for much less classical forms, which require a different kind of description. The famous Spanish architect Felix Candela is a good example.

Candela thoroughly explored the properties of the hyperbolic paraboloid, allowing him to create works that are now references in the world of architecture. This figure illustrates the construction of one of the works by Félix Candela, where the fundamental element is precisely a thin concrete shell in the shape of a hyperbolic paraboloid. In his time, Candela performed all necessary calculations manually to determine the forms he intended to create. We will now see how we can get the same results in a much more efficient way.

The Lomas de Cuernavaca Chapel under construction. Photography by Dorothy Candela.

image

For this, we are going to generalize the parametric descriptions of curves so as to allow the specification of surfaces. Even though a curve is described by a triple of functions of a single parameter \(t\), for example, \((x(t), y(t), z(t))\), in the case of a surface it will have to be described by a triple of function of two parameters that vary independently,

Mathematically speaking, a parametric surface is simply the image of an injective function of \(\mathbb R^2\) in \(\mathbb R^3\).

for example, \((x(u,v), y(u,v), z(u,v))\). Obviously, the choice of rectangular, cylindrical, spherical coordinates or others is just a matter of convenience: in any case, three functions are required.

As it happens with curves, although we can describe a surface in an implicit form, the parametric representation is more adequate for the generation of the positions through which the surface passes. For example, a sphere centred at point \((x_0, y_0, z_0)\) and with radius \(r\), can be described implicitly by the equation

\[(x-x_0)^2+(y-y_0)^2+(z-z_0)^2-r^2=0\]

but this form does not allow the direct generation of coordinates, being more preferable the adoption of a parametric description, by the functions

\[\left\{ \begin{aligned} x(\phi,\psi) &= x_0 + r \sin\phi\cos\psi\\ y(\phi,\psi) &= y_0 + r \sin\phi\sin\psi\\ z(\phi,\psi) & =z_0 + r \cos\phi \end{aligned}\right.\]

To illustrate the concept of parametric surface, we are going to consider a famous surface: the Möbius Strip

The Möbius Strip is not a true surface, but a surface with boundary.

10.6.1 The Möbius Strip

The Möbius strip (also known as Möbius band) was described independently by two German mathematicians in 1858, first by Johann Benedict Listing and two months later by August Ferdinand Möbius. Although Listing was the only one who published this discovery, it was eventually baptised with the name of Möbius. This figure shows a picture of a Möbius band made from a paper strip and where it is possible to see one of the extraordinary features of this surface, it is possible to walk around it entirely without ever leaving the same "side" because, in truth, the Möbius strip only has one side.

This property has been exploited, for example, in transmission belts that, when adopting the topology of a Möbius strip, they wear out both "sides" of the belt instead of only one, thereby making them last twice as long.

Likewise, the surface is limited only by one edge, which extends to the entire surface. In fact, the Möbius strip is the simplest surface of only one side and one edge.

The Möbius Strip.

image

The Möbius strip has been successively used in various contexts, from the famous works of Maurits Cornelis Escher that employ a Möbius strip, as "Möbius Strip II", to the representative symbol of the recycling, which consists of three arrows arranged along a Möbius strip, as can be see in this figure.

The representative symbol of recycling: three arrows arranged along a Möbius strip.

image

The parametric equations of this surface, in cylindrical coordinates, are as follows:

\[\left\{ \begin{aligned} \rho(s,t)& =1 + t \cos\frac{s}{2}\\ \theta(s,t)& =s\\ z(s,t)& =t \sin\frac{s}{2} \end{aligned}\right.\]

Given the periodicity of the trigonometric functions, we have that \(\frac{s}{2} \in [0,2\pi]\) or \(s \in [0,4\pi]\). In that period, for a given \(t\), the \(z\) coordinate will assume symmetrical values in relation to the \(xy\) plane, in the limit, between \(-t\) and \(t\). This means that the Möbius strip develops up and down the \(xy\) plane.

To draw this surface we can use, as an initial approach, a generalization of the process we used to draw parametric curves. In this approach, we simply applied the argument function \(f(t)\) along a succession of \(n\) values ​​from \(t_0\) to \(t_1\). In the case of parametric surfaces, we intend on applying the function \(f(s,t)\) over a succession of \(m\) values ​​from \(s_0\) to \(s_1\), and for each one, over a succession of \(n\) values, from \(t_0\) to \(t_1\). Thus, the \(f(s,t)\) function will be applied to \(m\times n\) pairs of values. This behaviour is easily obtained at the expense of two chained mappings:

(map (lambda (s)
       (map (lambda (t)
              (f s t))
            (division t0 t1 n)))
     (division s0 s1 m))

For example, in the case of Möbius strip, we can define:

(define (mobius-strip s0 s1 m t0 t1 n)
  (map (lambda (s)
         (map (lambda (t)
                (cyl (+ 1 (* t (cos (/ s 2))))
                     s
                     (* t (sin (/ s 2)))))
              (division t0 t1 m)))
       (division s0 s1 n)))

In fact, this combination of mappings with interval divisions is so frequent that Rosetta already provides it through an extension of the map-division function, allowing us to write

(define (mobius-strip s0 s1 m t0 t1 n)
  (map-division (lambda (s t)
                  (cyl (+ 1 (* t (cos (/ s 2))))
                       s
                       (* t (sin (/ s 2)))))
                s0 s1 m t0 t1 n))

We have seen that in the case of a function with a single parameter \(f(t)\), we had that

(map-division f t0 t1 n)

was equivalent to

(map f
     (division t0 t1 n))

This equivalence is now extended to the case of functions that have two parameters \(f(s,t)\), that is,

(map-division f s0 s1 m t0 t1 n)

is equivalent to

(map (lambda (s)
       (map (lambda (t)
              (f s t))
            (division t0 t1 n)))
     (division s0 s1 m))

Naturally, if the interior mapping produces a list with the results of the application of the function \(f\), the exterior mapping will produce a list of lists of results of the application of the function \(f\). Assuming that each application of the function \(f\) yields a point in three-dimensional coordinates, we can easily see that this expression produces a list of lists of points. Thus, the result will be in the form of:

((P0,0 P0,1 ... P0,m)
 (P1,0 P1,1 ... P1,m)
 ...
 (Pn,0 Pn,1 ... Pn,m))

To draw this list of lists of points, we can simply apply the spline function to each of the lists of points:

(define (splines ptss)
  (map spline ptss))

Thus, we can easily represent a first preview of the Möbius strip:

(splines (mobius-strip 0 4pi 80 0 0.3 10))

The result of the evaluation of the above expression is represented in this figure.

The Möbius strip.

An interesting aspect of three-dimensional parametric surfaces is that the set of points that define them can be arranged in a matrix. In fact, if the parametric surface is obtained by applying the function \(f(s,t)\) over a succession of \(m\) values, from \(s_0\) to \(s_1\), and for each one, over a succession of \(n\) values ​​from \(t_0\) to \(t_1\), we can place the results of those applications in a matrix of \(m\) rows and \(n\) columns, as follows:

\[\begin{bmatrix} f(s_0,t_0) & \dots & f(s_0,t_1)\\ \dots & \dots & \dots\\ f(s_1,t_0) & \dots & f(s_1,t_1) \end{bmatrix}\]

What the map-division function returns is nothing more than a representation of this matrix, implemented in terms of a list of matrix rows, each row implemented in terms of a list of values corresponding to the elements of that row of the matrix.

It is now easy to see that the application of the function splines to the representation of the matrix does nothing else than to draw \(m\) splines, each defined by \(n\) points of each of the \(m\) rows of the matrix. Another alternative is to draw \(n\) splines defined by \(m\) points of each of the \(n\) columns of the matrix. For this, the simplest way is to transpose the matrix, i.e., change the lines with the columns, in the form:

\[\begin{bmatrix} f(s_0,t_0) & \dots & f(s_1,t_0)\\ \dots & \dots & \dots\\ f(s_0,t_1) & \dots & f(s_1,t_1) \end{bmatrix}\]

For this, we can define a function that, given a matrix implemented as a list of lists, returns the transposed matrix in the same implementation:

(define (transposed-matrix matrix)
  (if (null? (car matrix))
      (list)
      (cons (map car matrix)
            (transposed-matrix (map cdr matrix)))))

Using this function, it is now possible to trace orthogonal curves to the ones represented in this figure simply by tracing the obtained curves from the transposed matrix, i.e.

(splines (transposed-matrix (mobius-strip 0 4pi 80 0 0.3 10)))

The Möbius strip.

The result is shown in this figure.

Finally, to obtain a mesh with the combined effect of both curves, we can define an auxiliary function mesh-splines:

(define (mesh-splines ptss)
  (splines ptss)
  (splines (transposed-matrix ptss)))

We can now use this function as follows:

(mesh-splines (mobius-strip 0 4pi 80 0 0.3 10))

The Möbius strip.

The result is shown in this figure.

This type of representation based on the use of lines to give the illusion of an object, is called wireframe. Formally, a wireframe is nothing more than a set of lines that represent a surface. Wireframe models have the advantage, over other forms of visualization, of allowing a much faster drawing.

10.7 Surfaces

Wireframe models do not constitute surfaces. In fact, as the lines are infinitely thin and there is no "material" between them, it is not possible to associate a wireframe model to a real surface.

If what we want is, in fact, to draw surfaces, then it is preferable we exploit the capabilities of Rosetta for the creation of surfaces.

Polygon meshes are clusters of polygons, usually triangles and quadrilaterals which define the shape of an object. In comparison to models constituted only by lines, polygon meshes have the advantage of being more realistically displayed, for example, with the removing of invisible surfaces, with the inclusion of shading, etc. Obviously, each face of these polygonal meshes has zero thickness, therefore they are not actually solids but only abstract representations of surfaces. Even so, they can be very useful to obtain a more correct visualization of a surface and can be subsequently transformed for creating solids, for example, using the function thicken that gives the surface a uniform thickness.

Rosetta provides an operation for creating these polygonal meshes named surface-grid. This figure shows a realistic representation of the Möbius strip generated from the following expression:

(surface-grid (mobius-strip 0 4pi 80 0 0.3 10))

#<surface-grid 19606>

The Möbius strip.

It is now trivial to experiment with variations of the Möbius strip, for example, to vary the strip’s "width", as depicted in this figure.

The Möbius strip for different "widths". From left to right the variation interval in \(v\) is \([0,\frac{1}{2}]\), \([0,1]\), \([0,2]\) and \([0,4]\).

To visualize a more architectural example, let us consider the hyperbolic paraboloid used by Félix Candela in the Lomas Cuernavaca Chapel. This surface can be described in its implicit representation by the equation

\[x^2-y^2-z=0\]

or, likewise, through its parametric representation:

\[\left\{ \begin{aligned} x(s,t)& = s\\ y(s,t)& = t\\ z(s,t)& = s^2 - t^2 \end{aligned}\right.\]

The translation to Rosetta is trivial:

(thicken
  (surface-grid
    (map-division
      (lambda (x y)
        (xyz x y (- (* x x) (* y y))))
      -0.5 1.3 40
      -2 2 80))
  0.03)

thicken: undefined;

 cannot reference an identifier before its definition

In the previous expression, we used the domain limits that approximate the surface to the real work and we gave thickness to the surface through the function thicken. In this figure we show a view of the work where we eliminated the portion that is under the earth.

An approximation of the Lomas Cuernavaca Chapel.

10.7.1 Exercises 52
10.7.1.1 Question 220

Define a function that, with appropriate arguments, builds the surface shown in the following figure.

10.7.1.2 Question 221

Consider the following image:

Assume that the figure is centred at the origin and represented using an isometric perspective. Notice that the surface intersections with any plane parallel to the \(XZ\) plane or to the \(YZ\) plane produces a perfect sinusoid (though it produces sinusoids of different frequencies depending on the distance from the intersection plane to the origin). From these tips, try to figure out the parametric equations that originated it.

10.7.2 Helicoid

We discussed, in section (part "sec:helix"), the drawing of the helix. Let us now discuss the surface that generalizes this curve: the helicoid.

The helicoid was discovered in 1776 by Jean Baptiste Meusnier. Its parametric equations (in cylindrical coordinates) are:

\[\left\{ \begin{aligned} \rho(u,v)& =u\\ \theta(u,v)& =\alpha v\\ z(u,v)& =v \end{aligned}\right.\]

This figure shows two helicoids with different parameters, generated from the following fragment of program:

Two helicoids with variation intervals in \(v\) of \([0,2\pi]\). The left helicoid has \(\alpha=1\), and variation interval in \(u\) of \([0,1]\) while the right one has \(\alpha=3\) and \(u \in [0,5]\).

(define (helicoid p a u0 u1 m v0 v1 n)
  (map-division
    (lambda (u v)
      (+cyl p
            u
            (* a v)
            v))
    u0 u1 m v0 v1 n))
(surface-grid (helicoid (xyz 0 0 0) 1 0 1 10 0 2pi 100))

#<surface-grid 19607>

(surface-grid (helicoid (xyz 7 0 0) 3 0 5 50 0 2pi 200))

#<surface-grid 19608>

An interesting feature of the helicoid is that for each point of the helicoid there is a helix that passes though it.

10.7.3 Spring

A spring is another geometric figure that has affinities with an helix. Mathematically, the spring is the volume generated by a circle that travels along an helix. In simpler terms, a spring is an helix with "thickness", i.e., a tube that wraps around an axis. If \(r_0\) is the radius of the tube, \(r_1\) the distance from the tube axis to the helix axis, \(\alpha\) the initial rotation angle of the spring and \(v_z\) the "speed" along the \(z\) axis, the parametric equations of the spring are:

\[\left\{ \begin{aligned} \rho(u,v)& =r_1+r_0\cos u\\ \theta(u,v)& =\alpha+v\\ z(u,v)& = \frac{v_z v}{\pi} + r_0\sin u \end{aligned}\right.\]

The translation of the above definition into Racket is:

(define (spring p a r0 r1 vz u0 u1 m v0 v1 n)
  (map-division
    (lambda (u v)
      (+cyl p
            (+ r1 (* r0 (cos u)))
            (+ a v)
            (+ (/ (* vz v) pi) (* r0 (sin u)))))
    u0 u1 m v0 v1 n))

In this figure we find visualizations of a spring in its extension process. Each image corresponds to a spring with the same dimensions, except in regards to the "speed" in \(z\).

(surface-grid
  (spring (xyz  0 0 0) 0 1 5 1 0 2pi 50 0 (* 3 2pi) 150))

#<surface-grid 19609>

(surface-grid
  (spring (xyz 20 0 0) 0 1 5 2 0 2pi 50 0 (* 3 2pi) 150))

#<surface-grid 19610>

(surface-grid
  (spring (xyz 40 0 0) 0 1 5 4 0 2pi 50 0 (* 3 2pi) 150))

#<surface-grid 19611>

Three springs created with the parameters \(r_0=1\), \(r_1=5\), \(u \in [0, 2\pi]\) and \(v \in [0, 6\pi]\). From left to right, we have the speed \(vz \in \{1,2,4\}\).

10.7.4 Exercises 53
10.7.4.1 Question 222

The traditional spring is just the simplest form that is possible to generate from the function spring. More elaborate shapes can be generated by either parametrization, or by composition. For example, by composing "springs" we can generate ropes. In this case, we treat the "springs" as "strands" or "filaments" which, wrapped around each other, constitute a rope.

Thus, a rope can be seen as a set of intertwined springs, with all the springs developing along the same axis but "rotated" (i.e., with an initial angle) and a step (i.e., a velocity of development) which allows them to be close to each other. In the following Figure we have represented two diagrams that show, on the left, the position of the springs in the case of a rope with four "strands". This rope can be generalized from any number of strands, to the lower limit of two strands, shown on the right.

Define the function rope that, by receiving the appropriate parameters, creates ropes. For example, consider the following expressions of which the evaluation produces three ropes built with two, three and six strands, shown below:

(rope (xyz  0 0 0) 2 1 3)
(rope (xyz 10 0 0) 3 1 2)
(rope (xyz 20 0 0) 6 1 1)

10.7.4.2 Question 223

A breather is a mathematical surface that characterizes a particular type of wave which is illustrated in the following Figure. Its parametric equation is::

\[\left\{ \begin{aligned} x(u,v)& = -u+\frac{2\left(1-a^2\right)\cosh(au)\sinh(au)}{a\left(\left(1-a^2\right)\cosh^2(au)+a^2\,\sin^2\left(\sqrt{1-a^2}v\right)\right)} \\ y(u,v)& = \frac{2\sqrt{1-a^2}\cosh(au)\left(-\sqrt{1-a^2}\cos(v)\cos\left(\sqrt{1-a^2}v\right)-\sin(v)\sin\left(\sqrt{1-a^2}v\right)\right)}{a\left(\left(1-a^2\right)\cosh^2(au)+a^2\,\sin^2\left(\sqrt{1-a^2}v\right)\right)} \\ z(u,v)& = \frac{2\sqrt{1-a^2}\cosh(au)\left(-\sqrt{1-a^2}\sin(v)\cos\left(\sqrt{1-a^2}v\right)+\cos(v)\sin\left(\sqrt{1-a^2}v\right)\right)}{a\left(\left(1-a^2\right)\cosh^2(au)+a^2\,\sin^2\left(\sqrt{1-a^2}v\right)\right)} \end{aligned}\right.\]

Note that due to the periodicity of the trigonometric functions, we have that \(-2\pi\leq u \leq 2\pi\) and \(-12\pi\leq v \leq 12\pi\). The \(a\) parameter characterizes different surfaces and may vary between \(0\) and \(1\).

Define the function breather that draws the surface in question from a point \(p\), the parameter \(b\) and the variation limits and the number of points to consider along that variation, respectively, for the parameters \(u\) and \(v\). For example, the previous figure was produced by evaluating the expression:

(surface-grid
  (breather (xyz 0 0 0) 0.4 -13.2 13.2 200 -37.4 37.4 200))
10.7.5 Shells

Several authors argue that nature is a mathematical manifestation and justify this belief by the striking resemblance that some mathematical surfaces exhibit with natural forms.

The Dini surface, is one such cases. The parametric equations that define it are:

\[\left\{ \begin{aligned} x(u,v)& =\cos(u)\sin(v)\\ y(u,v)& =\sin(u)\sin(v)\\ z(u,v)& =\cos(v)+\log(\tan(\frac{v}{2}))+a*u \end{aligned}\right.\]

where \(0\leq u\leq 2\pi\) and \(0\leq v\leq\pi\).

The translation of these equations into Racket is direct:

(define (dini p a u0 u1 m v0 v1 n)
  (map-division
    (lambda (u v)
      (+xyz p
            (* (cos u) (sin v))
            (* (sin u) (sin v))
            (+ (cos v) (log (tan (/ v 2.0))) (* a u))))
    u0 u1 m
    v0 v1 n))

The following invocations of this function show how it is possible, in fact, to simulate the natural shapes of certain types of shells. The result is represented in this figure.

(surface-grid
  (dini (xyz 0 0 0) 0.2 0 (* 6 pi) 100 0.1 (* pi 0.5) 100))
(surface-grid
  (dini (xyz 3 0 0) 0.2 0 (* 6 pi) 100 0.1 (* pi 0.7) 100))
(surface-grid
  (dini (xyz 6 0 0) 0.2 0 (* 6 pi) 100 0.1 (* pi 0.9) 100))

The Dini surface as a mathematical approximation of the form of certain types of shells.

Another example is the spiral shell, defined mathematically (in Racket) by:

(define (shell p a b u0 u1 m v0 v1 n)
  (map-division
    (lambda (u v)
      (let ((e (exp (/ u 6.0 pi)))
            (c (expt (cos (/ v 2.0)) 2)))
        (+xyz p
              (* a (- 1 e) (cos u) c)
              (* b (- e 1) (sin u) c)
              (+ (- 1 (exp (/ u 3.0 pi)) (sin v))
                 (* e (sin v))))))
    u0 u1 m
    v0 v1 n))

From the previous function, we can experiment variations like the ones presented below, and which generate the surfaces represented in this figure.

(surface-grid
  (shell (xyz 0 0 0) 1 1 0 (* 7 pi) 100 0 (* 2 pi) 100))
(surface-grid
  (shell (xyz 7 0 0) 2 2 0 (* 7 pi) 100 0 (* 2 pi) 100))
(surface-grid
  (shell (xyz 18 0 0) 3 1 0 (* 7 pi) 100 0 (* 2 pi) 100))

A mathematical approximation of the shape of spiral shells.

10.7.6 Cylinders, Cones, and Spheres

Although we have illustrated the previous section with relatively complex surfaces, in this section we will start by doing surface that are comparatively much simpler, which will allow us to realize that the parametric construction of surfaces is relatively easy as long as we understand the impact of the variation of the independent parameters \(u\) and \(v\).

For example, the surface of a cylinder of radius \(r\) is characterized by being the set of points that are at a fixed distance \(r\) of an axis and from a minimum \(h_0\) and maximum \(h_1\) "height" relative to that axis.

Assuming, for simplicity, that we will match the cylinder axis with the \(Z\) axis, this means that we employ cylindrical coordinates \((\rho, \phi,z)\), with the cylinder surface being defined simply by fixing \(\rho=r\) and letting \(\phi\) vary between \(0\) and \(2\pi\) and \(z\) vary between \(h_0\) and \(h_1\). With \(\rho\) being fixed and the variations of \(\phi\) and \(z\) being independent, the use of parametric surfaces is trivial: we need only use one of the parameters \(u\) or \(v\) to make the variation of \(\phi\) and the other for the variation of \(z\).

More specifically, let us make \(\rho(u,v)=r\) and let \(\phi(u,v)=u\) and \(z(u,v)=v\) vary independently, respectively between \(u\in [0,2\pi]\) and \(v\in [h_0,h_1]\). Since \(\phi\) depends directly and exclusively on \(u\) and \(z\) depends directly and exclusively on \(v\), we can use these parameters directly in the function. This way, we are able to write a function that produces a cylindrical coordinate from the value of the radius \(r\) and from the independent parameters \(\phi\) and \(z\).

The image on the left in this figure shows the cylinder of radius \(1\) between \(h_0=-1\) and \(h_1=1\), produced by the evaluation of following expression:

(surface-grid
  (map-division
    (lambda (fi z)
      (cyl 1 fi z))
    0 2pi 60
    -1 1 20))

If, instead of fixing the radius of \(\rho(fi,z)=r\), we make it vary in function to the height \(z\), then it is obvious that the radius of the "cylinder" will be zero at the origin and will increase (in absolute value) as it moves away from the origin. The only difference to the previous case will thus be on the expression that creates the cylindrical coordinates, which will now be (cyl z fi z), reflecting the linear increase of the radius with \(z\). Evidently, the resulting figure will not be a cylinder but a cone, that we present in the central image of this figure.

Finally, let us consider the description of a sphere of radius \(r\) in spherical coordinates \((\rho, \phi, \psi)\), which is reduced to \(\rho=r\), with \(\phi\) varying between \(0\) and \(2\pi\) and \(\psi\) varying between \(0\) and \(\pi\). Once again, we need two independent variations, for which we can arbitrarily assign \(u\) to one and \(v\) to the another. Thus, let us make \(\rho(u,v)=r\), \(\phi(u,v)=u\) and \(\psi(u,v)=v\) with \(u\in [0,2\pi]\) and \(v\in [0,\pi]\). As is was did in the two previous examples, this association between \(\phi\) and \(\psi\) and, respectively, \(u\) and \(v\), allows us to write the function directly in terms of the former. Exemplifying with \(r=1\), we have:

(surface-grid
  (map-division
    (lambda (fi psi)
      (sph 1 fi psi))
    0 2pi 60
    0 pi 30))

The result is on the right of this figure.

A cylinder, a cone and a sphere generated as parametric surfaces.

To see the impact that small changes in parameters can have on the shape of generated surfaces, let us try three variations on the shape of the sphere. The first variation is to add to the radius of the sphere a sinusoid with high frequency and low amplitude, in terms of "latitude" \(\psi\), for example, by making \(\rho=1+\frac{\sin(20\psi)}{20}\). The result will be a formation of waves from pole to pole, as is visible in the image on the right of this figure. If, on the other hand, we decide to make the same change in radius to be dependent on an identical sinusoid but in terms of \(\phi\), then the waves travel along the "longitude", for example, from east to west. This effect is illustrated on the central image of this figure. Finally, we can get a combined effect by making the radius simultaneously dependent on a sinusoid in terms of "latitude" and other sinusoid in terms of "longitude", i.e., \(\rho=1+\frac{\sin(20\phi)}{20}+\frac{\sin(20\psi)}{20}\). The result is shown on the right image of this figure.

Spheres of which the radius is a function of the "latitude", "longitude" or both.

10.7.7 Exercises 54
10.7.7.1 Question 224

Consider the following ellipsoid:

An ellipsoid is characterized by the dimensions of its three orthogonal radii \(a\), \(b\) and \(c\). Its parametric equation is:

\[\begin{aligned} x&=a\sin \psi \cos \phi\\ y&=b\sin \psi \sin \phi\\ z&=c\cos \psi\end{aligned}\] \[\begin{matrix}-\frac{\pi}{2}\leq\phi\leq+\frac{\pi}{2};\quad-\pi\leq\psi\leq+\pi;\end{matrix}\]

Define the ellipsoid function that produces the ellipsoid from the three radii \(a\), \(b\), \(c\), and also from the number \(n\) of values to use along \(\phi\) and \(\psi\).

10.8 Bodegas Ysios

The Bodegas Ysios is a building designed by Santiago Calatrava and intended for the production, storage and distribution of wines. this figure shows a frontal view of the building.

Bodegas Ysios. Photograph by Luis Antonio Ortuño.

image

The building has two longitudinal walls of sinusoidal shape of which the top finishing is also sinusoidal. These walls are 196 meters long and are spaced from one another by 26 meters along the midline. The roof has the shape of a wave made of parallelepipeds which rest on the tops of the sinusoidal walls. Note that the tops of the walls, where the parallelepiped rest, are generally at different heights, since the two top sinusoids have opposite phases with respect to one another.

The opposing phases means that when one of the sinusoids reaches its maximum value, the other reaches its minimum and vice versa. For this to happen, the difference between phases of the two sinusoids has to be \(\pi\).

As can be seen in this figure, ​​the central part of the south facade, intended to accommodate the visitor center, is more prominent and higher than the rest of the building.

As shown in this figure, the roof parallelepipeds are made of aluminium, in contrast with the cedar wood that is used on the south facade. On the north facade, concrete is used with some small openings for illumination. The east and west facades consist of wavy aluminium plates.

Bodegas Ysios. Photograph by Koikile.

image

In this section we will address the modelling of the frontal wall of the wine cellar. In a first analysis, we can model this wall as a vertical extrusion of a sinusoid. Unfortunately, although this operation can properly model the lateral sides of the wall, it can not do so for the central part because, in this area, the wall has a more complex evolution that moves away from the vertical.

A second approach could be the interpolation of regions, but this will only be achievable if we can shape the curves that delimit the wall. In addition to that, a large number of curves may be necessary in order to accurately represent the surface of the wall.

A third approach, more rigorous, consists in the mathematical description of the surface in question. This way, the required "curves" could be generated automatically. While it may be difficult to see at first glance that the wall of Bodegas Ysios can be modelled by a mathematical function, we will see that it is relatively simple to undertake this modelling through the composition and modification of simpler functions.

As a first approximation, we can start by considering a sinusoidal wall identical to what we would get if we were to make a vertical extrusion of a sinusoid. For simplicity, we will centre the wall base at the origin, which implies that the central hump has its maximum amplitude at the origin. Seeing as the sine amplitude is zero at the origin, this suggests that instead of a sine, it is preferable we use a cosine, of which the amplitude is maximum at the origin. The observation of this work by Calatrava indicates that this cosine performs three cycles for each side, for which the period should range between \(-6\pi\) and \(+6\pi\). Admitting that this wall evolves along the \(XZ\) plane, we can start by experimenting the following expression:

(surface-grid
  (map-division
    (lambda (x z)
      (xyz x
           (cos x)
           z))
    (* -7 pi) (* 7 pi) 100
    0 5 10))

The expression shows that we make the \(x\) coordinate vary freely with \(u\), we make the \(y\) coordinate be the \(x\) cosine, and finally we make the \(z\) coordinate vary freely with \(v\). As we can observe in this figure, the surface is a good approximation of the lateral sides of the front wall of the Bodegas Ysios.

First approach to modelling the Bodegas Ysios.

Let us now model the sinusoids at the top of the wall, where the roof’s parallelepipeds are supported. For this, we will simply "deform" the surface, causing the \(z\) coordinate to oscillate in a cos-sinusoidal way along the \(x\) axis, increasing the oscillation amplitude as we go up in \(z\). In order for the wall to have its base free of the influence of this cos-sinusoid, we will raise the cosine by one unit and use a reduced initial amplitude:

(surface-grid
  (map-division
    (lambda (x z)
      (xyz x
           (cos x)
           (* z (+ 1 (* 0.2 (cos x))))))
    (* -7 pi) (* 7 pi) 100
    0 5 10))

The result of the previous modelling is shown in this figure.

Second approach to modelling the Bodegas Ysios.

To model the increase in amplitude that the central "hump" will have with the height, we can consider increasing the cosine amplitude as the \(z\) height increases, by doing:

(surface-grid
  (map-division
    (lambda (x z)
      (xyz x
           (* (+ z 3) (cos x))
           (* z (+ 1 (* 0.2 (cos x))))))
    (* -7 pi) (* 7 pi) 100
    0 5 10))

The result, seen in this figure, shows that the evolution of the central part is already correctly modelled, but it is necessary to avoid that this change also affects the lateral sides. For this, all we need to do is a combination of two separate evolutions, one for the central area of the wall, corresponding to half of a cosine cycle, i.e., a range of variation of \(x\) between \(-\frac{\pi}{2}\) and \(+\frac{\pi}{2}\), and another for everything else. We must take into account that in order to compensate the slope difference caused by the increase of the \(y\) coordinate in the central area we must also increase the variation amplitude in \(z\). Thus, we have:

Third approach to modelling the Bodegas Ysios.

(surface-grid
 (map-division
  (lambda (x z)
    (xyz x
         (if (<= -pi/2 x pi/2)
           (* (+ z 3) 0.8 (cos x))
           (cos x))
         (if (<= -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))

The result of this change is shown in this figure.

Fourth approach to modelling the Bodegas Ysios.

The final format of the function already gives a good approximation to the shape of the cellar. This function can be seen as the mathematical representation of Calatrava’s ideas for the frontal wall of the Bodegas Ysios.

10.8.1 Exercises 55
10.8.1.1 Question 225

Define the function polygonal-prism that, from a list of coplanar points and a displacement \(d_x\) parallel to the \(x\) axis, creates a polygonal prism such as the one shown in the following diagram which was generated by (polygonal-prism (list ($ "P_0") ($ "P_1") ($ "P_2") ($ "P_3")) ($ "d_x"))

10.8.1.2 Question 226

Define a function roof-ysios capable of creating roofs with the same "style" as the Bodegas Ysios, but where the shape of the roof is relatively arbitrary. To allow this arbitrariness, assume that the roof is defined only by four lists with the same number of points (represented by their coordinates) and where the points that are in the same position in all four lists are in the same plane parallel to the \(YZ\) plane, such as outlined in the following figure:

Using one point of each of the four lists, use the function polygonal-prism (explained in the previous exercise) to create a prism in which the thickness \(d_x\) is equal to the distance between the planes defined by these points and by the next four points. The previous image shows two prisms constructed by this process.

10.8.1.3 Question 227

Define the function ysios-curve which, conveniently parametrized, is capable of generating a list with one of the vertices of each of the successive prisms that constitute a roof in the style of the Bodegas Ysios. The following image was generated by the function roof-ysios using four invocations of the function ysios-curve as arguments.

10.8.1.4 Question 228

Identify a combination of parameters for the functions ysios-walls and ysios-curve that is capable of approximately reproducing the facade and the roof of the Bodegas Ysios, as presented in the following image.

10.8.1.5 Question 229

The following image depicts a torus:

Deduce the parametric equation of the torus and define the Racket function torus that creates a torus similar to one on the previous image from the centre \(P\) of the torus, the greater \(r_0\) and smaller \(r_1\) radii and the number of intervals \(m\) and \(n\) to consider in each dimension.

10.8.1.6 Question 230

Redefine the previous function to generate a spheres torus similar to the one shown in the following image:

10.8.1.7 Question 231

The "apple" presented below can be described by the parametric equation:

\[\begin{aligned} x&=\cos u \cdot (4 + 3.8 \cos v)\\ y&=\sin u \cdot (4 + 3.8 \cos v)\\ z&=(\cos v + \sin v - 1) \cdot (1 + \sin v) \cdot \log(1 - \pi\cdot\frac{v}{10}) + 7.5 \sin v\end{aligned}\] \[\begin{matrix}0\leq u\leq 2\pi;\quad-\pi\leq v\leq\pi;\end{matrix}\]

Write a Racket expression that reproduces the previous "apple".

10.8.1.8 Question 232

Consider the "rose" presented below:

This "rose" was generated from a parametric equation discovered by Paul Nylander. Search for this equation and write a Racket expression that can generate the corresponding surface.

10.9 Surface Normals

When a surface is not planar, but is almost-planar, it can be difficult for an observer to perceive its curvature. In such cases, it is very useful to have a mechanism that allows us to visualize the effects of this curvature without altering the shape of the surface. The superimposition of normal vectors is a simple process to do this visualization, as can be seen in the images presented in this figure. On the left, we see a surface where the curvature is not perfectly obvious. On the right, we see the same surface but with the normal vectors clearly showing the curvature.

The image on the left shows an almost flat surface. The image on the right shows the same surface with overlapping normal vectors, allowing a better understanding of its curvature.

To overlap the normal vectors we have to calculate their position and direction. Assuming that the surface is discretized in a mesh (as produced, for example, by the function map-division), that mesh is composed by a succession of quadrangles that extend along two directions. To see the curvature, we can position each vector at the centre of each of these quadrangles, according to that quadrangle’s normal direction.

The centre of a quadrangle as the average midpoint of the diagonal midpoints.

To calculate the centre of a quadrangle we can employ the approach shown in this figure. The centre of the quadrangle defined by the points \(P_0\), \(P_1\), \(P_2\) and \(P_3\) can be obtained by determining the midpoints \(M_{02}\) and \(M_{13}\) of the quadrangle’s diagonals and, finally, the midpoint \(M\) of this segment. Translating this into Racket we simply have:

(define (centre-quadrangle p0 p1 p2 p3)
  (midpoints
    (midpoints p0 p2)
    (midpoints p1 p3)))

Normal at the centre of a quadrangle (planar on the left and non-planar on the right).

The calculation of the normal of a quadrangle is trivial when the quadrangle is planar, as it is enough to perform the cross product of two edges that meet at the same vertex, as we can see on the left of this figure. However, in the case of a non-planar quadrangle (on the right of this figure), that logic can i no longer applicable, because there may be different normals at each vertex. It is however possible to compute an approximate normal using Newell’s method [Sutherland et al 1974] which is based on the fact that the projections of the areas of a polygon onto the Cartesian planes are proportional to the components of the normal vector of that polygon. Newell’s method calculates these areas and, from them, derives the normal vector \(\vec{N}\). Mathematically speaking, given a polygon with \(n\) vertices \(v_0,v_1,\ldots,v_n\), the method consists in calculating the components of the normal vector \(\vec{N}\) through the sum of areas:

\[N_x=\sum_{i=0}^{n}(v_{i_y}-v_{{i+1}_y})\cdot(v_{i_z}+v_{{i+1}_z})\] \[N_y=\sum_{i=0}^{n}(v_{i_z}-v_{{i+1}_z})\cdot(v_{i_x}+v_{{i+1}_x})\] \[N_z=\sum_{i=0}^{n}(v_{i_x}-v_{{i+1}_x})\cdot(v_{i_y}+v_{{i+1}_y})\]

To transform the resulting vector into a unit vector, we simply have to normalize it, i.e., divide each component by its length. Translated into Racket, we have:

(define (polygon-normal pts)
  (normalized-vector
   (cross-products
    (append pts (list (car pts))))))
(define (cross-products pts)
  (if (null? (cdr pts))
    (xyz 0 0 0)
    (+c (cross-product (car pts) (cadr pts))
        (cross-products (cdr pts)))))
(define (cross-product p0 p1)
  (xyz (* (- (cy p0) (cy p1)) (+ (cz p0) (cz p1)))
       (* (- (cz p0) (cz p1)) (+ (cx p0) (cx p1)))
       (* (- (cx p0) (cx p1)) (+ (cy p0) (cy p1)))))
(define (normalized-vector v)
  (let ((l (sqrt (+ (sqr (cx v))
                    (sqr (cy v))
                    (sqr (cz v))))))
    (xyz (/ (cx v) l)
         (/ (cy v) l)
         (/ (cz v) l))))

In the case of a quadrangle, we can simply define

(define (quadrangle-normal p0 p1 p2 p3)
  (polygon-normal (list p0 p1 p2 p3)))

Truth be told, some of the operations previously outlined are predefined in Rosetta, particularly, the cross product (called cross-c), and the normalized vector (called norm-c).

To visually represent the normal we are going to use a very thin cylinder of which the size will be proportional to the size of the quadrangle, so that it automatically adapts to different quadrangles. Naturally, it is also possible to employ other approaches, for example, adopting fixed dimensions.

(define (normal pt0 pt1 pt2 pt3)
  (let ((c (quadrangle-centre pt0 pt1 pt2 pt3))
        (n (quadrangle-normal pt0 pt1 pt2 pt3))
        (d (distance pt0 pt2)))
    (cylinder c
              (/ d 40)
              (+c c (*c n (* d 1.5))))))

The next step is to go through all the mesh quadrangles and construct the normal in each one. For this, we do:

(define (normals ptss)
  (for/list ((pts0 ptss)
             (pts1 (cdr ptss)))
    (for/list ((p0 pts0)
               (p1 pts1)
               (p2 (cdr pts1))
               (p3 (cdr pts0)))
      (normal p0 p1 p2 p3))))

Even though the visualization of the normals is complementary to the visualization of the surface, it does not make much sense to see the first without also viewing the second. Therefore, we will define a function that combines the two views:

(define (surface-and-normals ptss)
  (normals ptss)
  (surface-grid ptss))

To test this function we can now try to apply it to the generation of normals on the Möbius strip:

(surface-and-normals (mobius-strip 0 4pi 160 0 0.3 5))

quadrangle-centre: undefined;

 cannot reference an identifier before its definition

This figure shows the result.

The Möbius surface with the superimposed normals.

10.10 Surface Processing

A careful observation of the functions normal-curves and normal-strip-curves shows that they iterate along the surface quadrangles, computing the normal at each one. Naturally, we can generalize this process so that it is possible to process a surface by applying any operation to all its quadrangles. The transition to a higher-order function is trivial:

(define (iterate-quadrangles f ptss)
  (for/list ((pts0 ptss)
             (pts1 (cdr ptss)))
    (for/list ((p0 pts0)
               (p1 pts1)
               (p2 (cdr pts1))
               (p3 (cdr pts0)))
      (f p0 p1 p2 p3))))

From now on, the computation of the normals to a surface can be treated as a mere particularization of the iterate-quadrangles function:

(define (normals ptss)
  (iterate-quadrangles normal ptss))

The great advantage, however, lies in the fact that we can now create many other ways of processing a surface. As an example, let us consider the creation of a net, as shown in this figure. For this example, the shape of the net is defined by the following parametric surface:

\[\left\{ \begin{aligned} x(u,v)& = u\\ y(u,v)& = v\\ z(u,v)& = \frac{7}{100}(\sin(u)+\sin(2(v-2))) \end{aligned}\right.\] \[0\leq u \leq 5\] \[0\leq v \leq 5\]

A net made from crossed cylinders.

The creation of the previous net can be done by simply creating a cross along the quadrangles of a surface. The cross can be modelled by using two cylinders of which the ends coincide with the vertices of the diagonals of the quadrangle. For the cylinders to adapt to different quadrangles, we can consider that the radius of the cylinders is proportional to the size of the quadrangle. The following function implements this approach:

(define (cylinders-cross-quad p0 p1 p2 p3)
  (let ((r (/ (min (distance p0 p1) (distance p0 p3)) 10)))
    (cylinder p0 r p2)
    (cylinder p1 r p3)))
To create the net of this figure we just need to do:

(iterate-quadrangles
 cylinders-cross-quad
 (map-division (lambda (i j)
                 (xyz i j (* 0.07 (+ (sin i) (sin (* 2 (- j 2)))))))
               0 5 20
               0 5 20))

cylinder: undefined;

 cannot reference an identifier before its definition

The function iterates-quadrangles can therefore be used either for different surfaces or for different functions to iterate over the coordinates of those surfaces. This figure shows different constructions performed over the surface defined by the following parametric function:

\[\left\{ \begin{aligned} x(u,v)& = u\\ y(u,v)& = v\\ z(u,v)& = \frac{4}{10}(\sin(u+v)+e^{\frac{\left|u-1\right|}{10}}+\sin(v-1)) \end{aligned}\right.\] \[0\leq u \leq 3\] \[0\leq v \leq 6\]

Different constructions made ​​from a surface. From left to right and top to bottom, the repeating element is: (1) four cylinders connected to a sphere; (2) a cube; (3) a cone; (4) a spherical cap; (5) a cylinder; (6) a parallelepiped of random height.

In this figure we show the result of the very same functions but now iterated over another surface defined by:

\[\left\{ \begin{aligned} x(u,v)& = u\\ y(u,v)& = v\\ z(u,v)& = \frac{4}{10}\sin(u\cdot v) \end{aligned}\right.\] \[-\pi\leq u \leq \pi\] \[-\pi\leq v \leq \pi\]}

Different constructions made ​​from a surface.

10.10.1 Exercises 56
10.10.1.1 Question 233

For each of the examples shown in this figure (or, equivalently, in this figure), define the function that receives four points as arguments and that, when iterated by the function iterates-quadrangle along the surface coordinates, reproduces the model.

10.10.1.2 Question 234

Define a function that, given the coordinates of the four vertices of a quadrangle, creates two interlaced cylinders along the diagonals of the quadrangle, as presented in the following image:

Use the function that you created as an argument of iterates-quadrangle in order to generate the "fabric" that is presented below:

10.10.1.3 Question 235

The approach used in the previous exercise is not adequate when the surface in question is not almost-planar because the corresponding cylinders corresponding to two adjacent quadrangles might not be aligned properly, case in which overlapping will occur or, worse yet, empty spaces.

Redefine the process of creating the fabric so that interlaced cylinders are used, but where each cylinder corresponds a complete wire (and not just the section contained in a quadrangle). To simplify, we can assume that the wires develop along the lines and columns of the coordinate matrix of the surface, so as to generate images as the following:

We have seen in several previous examples a separation between the generation of a surface’s coordinates (using, for example, the map-division function), and the use of these coordinates, for example, for visualization (using the superface-grid function) or for processing (using the iterates-quadrangles).

This separation is advantageous because it allows us to arbitrarily combine different functions for generating coordinates with different functions that use those coordinates. Another interesting possibility is the use of functions that process the coordinates to produce new coordinates. A trivial example would be the application of a scale effect over a surface, simply by multiplying the scale by each of the coordinates, thereby generating a new set of coordinates.

A slightly less trivial example, but far more interesting, is the creation of trusses of which the shape follows a surface. As we have seen in section Trusses, these trusses are composed of spheres joined by beams to form quadrangular pyramids. To build a truss that follows a surface we can simply build each of the truss’ pyramids with their base set on the surface and their top located on the normal vector to that surface. The function spatial-truss (that we developed in section Spatial Trusses) is perfectly capable of building these trusses, as long as it receives the positions of the truss’ knots in the form of a list with an odd number of coordinates lists, according to the sequence base-knots, top-knots, base-knots, top-knots, ..., base-knots.

Bearing these assumptions in mind, to build a truss that follows a surface we can start by generating the coordinates of that surface, but conveniently spaced so as to serve as positions to the base knots of each truss pyramid. Next, for each quadrangle of coordinates, we have to find the pyramid’s top knot of which the base is this quadrangle. For this, we can assume that the trusses will use, whenever possible, beams with the same dimension \(l\). This implies that the top knot of each pyramid is located on the normal to the quadrangle defined by the base knots, at a distance from the centre of that quadrangle given by \(h=\frac{l}{\sqrt{2}}\). Therefore, we have to calculate both the centre and the quadrangle’s normal. The following function illustrates this process:

(define (vertex-quadrangular-pyramid p0 p1 p2 p3)
  (let ((h (/ (+ (distance p0 p1)
                 (distance p1 p2)
                 (distance p2 p3)
                 (distance p3 p0))
              4.0
              (sqrt 2))))
    (+c (quadrangle-centre p0 p1 p2 p3)
        (*c (quadrangle-normal p0 p1 p2 p3)
            h))))

The next step will be to use this function to compute the vertices’ coordinates along the surface mesh coordinates, creating new sequences of coordinates that are insert between the sequences of coordinates of the original mesh, in order to reproduce the alternation between base-knots and top-knots of the truss.

The two following functions perform this task:

(define (insert-vertex-pyramid ptss)
  (if (null? (cdr ptss))
    ptss
    (cons
     (car ptss)
     (cons (insert-vertex-pyramid-2 (car ptss) (cadr ptss))
           (insert-vertex-pyramid (cdr ptss))))))
(define (insert-vertex-pyramid-2 pts0 pts1)
  (cons (vertex-quadrangular-pyramid (car pts0) (car pts1) (cadr pts1) (cadr pts0))
        (if (null? (cddr pts0))
          (list)
          (insert-vertex-pyramid-2 (cdr pts0) (cdr pts1)))))

Finally, given any mesh, we simply have to chain the creation of vertices with the creating of the truss, i.e.:

(space-truss
  (insert-vertex-pyramid
    mesh))

space-truss: undefined;

 cannot reference an identifier before its definition

Truss built over a surface.

This figure shows the result of creating a truss over the same surface used in this figure. Naturally, we can combine the truss construction with any other surface. This figure shows a truss built over the same surface used in this figure.

Truss built over a surface.

Finally, out of curiosity, this figure shows a truss built over a Möbius strip. This truss was produced by the evaluation of the following expression:

(space-truss
  (insert-vertex-pyramid
    (mobius-strip2 0 4pi 160 0 0.3 5)))

space-truss: undefined;

 cannot reference an identifier before its definition

A truss with the shape of a Möbius strip.

p

10.10.2 Exercises 57
10.10.2.1 Question 236

If you look closely you will notice the truss has a "default". Identify it and explain it.

10.10.2.2 Question 237

Consider the "hedgehog" and "cactus" shown in the following image:

Define a function that, conveniently parametrized, is capable of creating "hedgehogs" and "cacti" as the previous ones.

11 Epilogue

We have seen throughout this book that programming may become an important tool for the architect. Programming allows us to formalize and communicate a thought, enables us to accurately model forms that exist only in our imagination, allows us to automate tedious and repetitive tasks and it also allows us to generate forms that we had not even thought of. All these capabilities justify that programming should get the attention it deserves.

It is precisely this attention that large architectural practices have been giving in recent years and, in fact, mastery of programming starts to be a frequent requirement for admission.

Learning to program is, therefore, imperative. Similar to many other disciplines, it is a learning process that requires effort, dedication, rigour, and attention to detail. But it is also a learning experience with a huge return on investment. Those who master programming become more capable and more efficient.

We have chosen, in this work, to teach programming in the context of Architecture. For that, we selected the topics that would have an immediate applicability in Architecture and, in reality, many of the examples used were actually provided by architects.

Despite the scale of this work, we should keep in mind that it illustrates only an infinitesimal fraction of what we can consider as Programming. Countless other works will have to be (continuously) written for this fraction to start having meaning. The recommended bibliography list has some additional sources of information for readers interested in deepening their knowledge.

12 Operations

This section describes the data types and operations available in Rosetta.

 (require rosetta/tikz) package: rosetta

12.1 Types

Rosetta explores several different types of entities, from numbers to geometric shapes.

syntax

Real

The type of real numbers such as 1, 2/3, and 1.5.

syntax

Integer

The type of integers numbers such as 1, 2, and 12345.

syntax

Loc

The type of locations (also known as positions. The members of this type are particular locations in space, such as (xy 1 2), (pol 1 pi), and (xyz 3 2 1). Locations are always specified in relation to some specific coordinate space, frequently, implicitly defined.

syntax

Vec

The type of vectors. The members of this type are particular vectors in space, such as (vxy 1 2), (vpol 1 pi), and (vxyz 3 2 1). Vectors are always specified in relation to some specific coordinate space, frequently, implicitly defined.

syntax

Cs

The type of coordinate space. The members of this type are particular coordinate spaces.

syntax

Shape

The type of shapes. The members of this type includes the values produced by functions such as circle, line, and sphere.

12.2 Coordinate Space

In Rosetta, every location references a particular coordinate space, by default, the World Coordinate Space.

value

world-cs : Cs

The world coordinate space.

Operations that create locations, such as xyz or pol, have an optional parameter for specifying the coordinate space that should be used. If omitted, that parameter defaults to the value of (current-cs).

parameter

(current-cs)  Cs

(current-cs Cs)  void?
  Cs : Cs
 = world-cs
A parameter that defines the current coordinate space for operations that create locations. Default value is the world coordinate space.

syntax

(with-cs expression body ...)

Temporarily switches (current-cs) to be the value of expression, evaluates body and restores the previous value of (current-cs), returning the value of the last expression in body.

12.3 Locations

Rosetta provides a large number of operations for specifying locations. Most of the operations that create locations accept a coordinate space as an optional argument, so that the location is relative to this coordinate space.

procedure

(xyz x y z [cs])  Loc

  x : Real
  y : Real
  z : Real
  cs : Cs = (current-cs)
Returns a location in the coordinate space cs defined by the Cartesian coordinates x, y, and z.

Example:

> (xyz 1 2 3)

#<xyz:1 2 3>

> (xyz (* 2 3) (+ 4 1) (- 6 2))

#<xyz:6 5 4>

procedure

(cx p)  Real

  p : Loc
Returns the first Cartesian coordinate of location p.

Example:
> (cx (xyz 1 2 3))

1

> (cx (xyz (* 2 3) (+ 4 1) (- 6 2)))

6

procedure

(cy p)  Real

  p : Loc
Returns the second Cartesian coordinate of location p.

Example:
> (cy (xyz 1 2 3))

2

> (cy (xyz (* 2 3) (+ 4 1) (- 6 2)))

5

procedure

(cz p)  Real

  p : Loc
Returns the third Cartesian coordinate of location p.

Example:
> (cz (xyz 1 2 3))

3

> (cz (xyz (* 2 3) (+ 4 1) (- 6 2)))

4

procedure

(x [x cs])  Loc

  x : Real = 1
  cs : Cs = (current-cs)
Returns a location in the coordinate space cs defined by the Cartesian coordinates x, 0, and 0, that is, on the \(X\) axis.

Example:
> (x 5)

#<xyz:5 0 0>

> (x)

#<xyz:1 0 0>

procedure

(y [y cs])  Real

  y : Real = 1
  cs : Cs = (current-cs)
Returns a location in the coordinate space cs defined by the Cartesian coordinates 0, y, and 0, that is, on the \(Y\) axis.

Example:
> (y 3)

#<xyz:0 3 0>

> (y)

#<xyz:0 1 0>

procedure

(z [z cs])  Real

  z : Real = 1
  cs : Cs = (current-cs)
Returns a location in the coordinate space cs defined by the Cartesian coordinates 0, 0, and z, that is, on the \(Z\) axis.

Example:
> (z 10)

#<xyz:0 0 10>

> (z)

#<xyz:0 0 1>

procedure

(xy x y [cs])  Real

  x : Real
  y : Real
  cs : Cs = (current-cs)
Returns a location in the coordinate space cs defined by the Cartesian coordinates x, y, and 0, that is, on the \(XY\) plane.

Example:
> (xy 2 5)

#<xyz:2 5 0>

> (xy 0 0)

#<xyz:0 0 0>

procedure

(xz x z [cs])  Real

  x : Real
  z : Real
  cs : Cs = (current-cs)
Returns a location in the coordinate space cs defined by the Cartesian coordinates x, 0, and z, that is, on the \(XZ\) plane.

Example:
> (xz 1 8)

#<xyz:1 0 8>

procedure

(yz y z [cs])  Real

  y : Real
  z : Real
  cs : Cs = (current-cs)
Returns a location in the coordinate space cs defined by the Cartesian coordinates 0, y, and z, that is, on the \(YZ\) plane.

Example:
> (yz 3 7)

#<xyz:0 3 7>

procedure

(+xyz p dx dy dz)  Loc

  p : Loc
  dx : Real
  dy : Real
  dz : Real
Returns a location in the coordinate space of location p defined by adding to location p the translation vector defined by the Cartesian coordinates dx, dy, and dz.

Example:
> (+xyz (xyz 0 0 0) 1 2 3)

#<xyz:1 2 3>

> (+xyz (xyz 1 2 3) 4 5 6)

#<xyz:5 7 9>

procedure

(+x p dx)  Loc

  p : Loc
  dx : Real
Returns a location in the coordinate space of location p defined by adding to location p the translation vector defined by the Cartesian coordinates dx, 0, and 0.

Example:
> (+x (xyz 0 0 0) 5)

#<xyz:5 0 0>

procedure

(+y p dy)  Loc

  p : Loc
  dy : Real
Returns a location in the coordinate space of location p defined by adding to location p the translation vector defined by the Cartesian coordinates 0, dy, and 0.

Example:
> (+y (xyz 1 2 3) 5)

#<xyz:1 7 3>

procedure

(+z p dz)  Loc

  p : Loc
  dz : Real
Returns a location in the coordinate space of location p defined by adding to location p the translation vector defined by the Cartesian coordinates 0, 0, and dz.

Example:
> (+z (xyz 1 2 3) 3)

#<xyz:1 2 6>

procedure

(+xy p dx dy)  Loc

  p : Loc
  dx : Real
  dy : Real
Returns a location in the coordinate space of location p defined by adding to location p the translation vector defined by the Cartesian coordinates dx, dy, and 0.

Example:
> (+xy (xyz 1 2 3) 1 3)

#<xyz:2 5 3>

procedure

(+xz p dx dz)  Loc

  p : Loc
  dx : Real
  dz : Real
Returns a location in the coordinate space of location p defined by adding to location p the translation vector defined by the Cartesian coordinates dx, 0, and dz.

Example:
> (+xz (xyz 1 2 3) 1 5)

#<xyz:2 2 8>

procedure

(+yz p dy dz)  Loc

  p : Loc
  dy : Real
  dz : Real
Returns a location in the coordinate space of location p defined by adding to location p the translation vector defined by the Cartesian coordinates 0, dy, and dz.

Example:
> (+yz (xyz 2 1 5) 1 2)

#<xyz:2 2 7>

procedure

(pol rho phi [cs])  Loc

  rho : Real
  phi : Real
  cs : Cs = (current-cs)
Returns a location in the coordinate space cs defined by the polar coordinates rho and phi, corresponding to the cylindrical coordinates rho, phi, and 0.

Example:
> (pol 1 0)

#<xyz:1 0 0>

> (pol (sqrt 2) (/ pi 4))

#<xyz:1.0000000000000002 1.0 0>

> (pol 1 (/ pi 2))

#<xyz:6.123233995736766e-17 1.0 0>

> (pol 1 pi)

#<xyz:-1.0 1.2246467991473532e-16 0>

procedure

(pol-rho p)  Real

  p : Loc
Returns the first polar coordinate of location p.

Example:
> (pol-rho (pol 10 (/ pi 3)))

10.0

> (pol-rho (xyz 1 2 3))

2.23606797749979

procedure

(pol-phi p)  Real

  p : Loc
Returns the second polar coordinate of location p.

Example:
> (pol-phi (pol 10 (/ pi 3)))

1.0471975511965976

> (pol-phi (xyz 1 2 3))

1.1071487177940904

procedure

(+pol p drho dphi)  Loc

  p : Loc
  drho : Real
  dphi : Real
Returns a location in the coordinate space of location p defined by adding to location p the translation vector defined by the polar coordinates drho and dphi.

Example:
> (+pol (xy 1 2) (sqrt 2) (/ pi 4))

#<xyz:2.0 3.0 0>

> (+pol (xy 1 2) 1 0)

#<xyz:2 2 0>

> (+pol (xy 1 2) 1 (/ pi 2))

#<xyz:1.0 3.0 0>

procedure

(cyl rho phi z [cs])  Loc

  rho : Real
  phi : Real
  z : Real
  cs : Cs = (current-cs)
Returns a location in the coordinate space cs defined by the cylindrical coordinates rho, phi, and z.

Example:
> (cyl 5 0 10)

#<xyz:5 0 10>

procedure

(+cyl p drho dphi dz)  Loc

  p : Loc
  drho : Real
  dphi : Real
  dz : Real
Returns a location in the coordinate space of location p defined by adding to location p the translation vector defined by the cylindrical coordinates drho, dphi, and dz.

Example:
> (+cyl (xyz 1 2 3) 3 pi/6 5)

#<xyz:3.598076211353316 3.5 8>

procedure

(sph rho phi psi [cs])  Loc

  rho : Real
  phi : Real
  psi : Real
  cs : Cs = (current-cs)
Returns a location in the coordinate space cs defined by the spherical coordinates rho, phi, and psi.

Example:
> (sph (sqrt 3) pi/4 (atan (sqrt 2)))

#<xyz:1.0 0.9999999999999999 0.9999999999999999>

procedure

(+sph p drho dphi dpsi)  Loc

  p : Loc
  drho : Real
  dphi : Real
  dpsi : Real
Returns a location in the coordinate space of location p defined by adding to location p the translation vector defined by the spherical coordinates drho, dphi, and dpsi.

Example:
> (+sph (xyz 1 2 3) 4 pi/4 pi/3)

#<xyz:3.4494897427831783 4.449489742783178 5.0>

12.4 Vectors

Vectors are used in Rosetta to specify entities that have a direction and a magnitude. Most of the operations that create vectors accept a coordinate space as an optional argument, so that the vector is related to this coordinate space.

procedure

(vxyz x y z [cs])  Loc

  x : Real
  y : Real
  z : Real
  cs : Cs = (current-cs)
Returns a vector in the coordinate space cs defined by the Cartesian coordinates x, y, and z.

Example:

> (vxyz 1 2 3)

#<vxyz:1 2 3>

> (vxyz (* 2 3) (+ 4 1) (- 6 2))

#<vxyz:6 5 4>

procedure

(cx v)  Real

  v : Loc
Returns the first Cartesian coordinate of vector v.

Example:
> (cx (vxyz 1 2 3))

1

> (cx (vxyz (* 2 3) (+ 4 1) (- 6 2)))

6

procedure

(cy v)  Real

  v : Loc
Returns the second Cartesian coordinate of location v.

Example:
> (cy (vxyz 1 2 3))

2

> (cy (vxyz (* 2 3) (+ 4 1) (- 6 2)))

5

procedure

(cz v)  Real

  v : Loc
Returns the third Cartesian coordinate of location v.

Example:
> (cz (vxyz 1 2 3))

3

> (cz (vxyz (* 2 3) (+ 4 1) (- 6 2)))

4

procedure

(vx [x cs])  Loc

  x : Real = 1
  cs : Cs = (current-cs)
Returns a vector in the coordinate space cs defined by the Cartesian coordinates x, 0, and 0, that is, on the \(X\) axis.

Example:
> (vx 5)

#<vxyz:5 0 0>

> (vx)

#<vxyz:1 0 0>

procedure

(vy [y cs])  Real

  y : Real = 1
  cs : Cs = (current-cs)
Returns a vector in the coordinate space cs defined by the Cartesian coordinates 0, y, and 0, that is, on the \(Y\) axis.

Example:
> (vy 3)

#<vxyz:0 3 0>

> (vy)

#<vxyz:0 1 0>

procedure

(vz [z cs])  Real

  z : Real = 1
  cs : Cs = (current-cs)
Returns a vector in the coordinate space cs defined by the Cartesian coordinates 0, 0, and z, that is, on the \(Z\) axis.

Example:
> (vz 10)

#<vxyz:0 0 10>

> (vz)

#<vxyz:0 0 1>

procedure

(vxy x y [cs])  Real

  x : Real
  y : Real
  cs : Cs = (current-cs)
Returns a vector in the coordinate space cs defined by the Cartesian coordinates x, y, and 0, that is, on the \(XY\) plane.

Example:
> (vxy 2 5)

#<vxyz:2 5 0>

procedure

(vxz x z [cs])  Real

  x : Real
  z : Real
  cs : Cs = (current-cs)
Returns a vector in the coordinate space cs defined by the Cartesian coordinates x, 0, and z, that is, on the \(XZ\) plane.

Example:
> (vxz 1 8)

#<vxyz:1 0 8>

procedure

(vyz y z [cs])  Real

  y : Real
  z : Real
  cs : Cs = (current-cs)
Returns a vector in the coordinate space cs defined by the Cartesian coordinates 0, y, and z, that is, on the \(YZ\) plane.

Example:
> (vyz 3 7)

#<vxyz:0 3 7>

procedure

(-vx [x cs])  Loc

  x : Real = 1
  cs : Cs = (current-cs)
Returns a vector in the coordinate space cs defined by the Cartesian coordinates -x, 0, and 0, that is, on the \(X\) axis.

Example:
> (-vx 5)

#<vxyz:-5 0 0>

> (-vx)

#<vxyz:-1 0 0>

procedure

(-vy [y cs])  Real

  y : Real = 1
  cs : Cs = (current-cs)
Returns a vector in the coordinate space cs defined by the Cartesian coordinates 0, -y, and 0, that is, on the \(Y\) axis.

Example:
> (-vy 3)

#<vxyz:0 -3 0>

> (-vy)

#<vxyz:0 -1 0>

procedure

(-vz [z cs])  Real

  z : Real = 1
  cs : Cs = (current-cs)
Returns a vector in the coordinate space cs defined by the Cartesian coordinates 0, 0, and -z, that is, on the \(Z\) axis.

Example:
> (-vz 10)

#<vxyz:0 0 -10>

> (-vz)

#<vxyz:0 0 -1>

procedure

(+vxyz v dx dy dz)  Loc

  v : Vec
  dx : Real
  dy : Real
  dz : Real
Returns a vector in the coordinate space of vector v defined by adding to vector v the translation vector defined by the Cartesian coordinates dx, dy, and dz.

Example:
> (+vxyz (vxyz 0 0 0) 1 2 3)

#<vxyz:1 2 3>

> (+vxyz (vxyz 1 2 3) 4 5 6)

#<vxyz:5 7 9>

procedure

(+vx v dx)  Loc

  v : Vec
  dx : Real
Returns a vector in the coordinate space of vector v defined by adding to vector v the translation vector defined by the Cartesian coordinates dx, 0, and 0.

Example:
> (+vx (vxyz 0 0 0) 5)

#<vxyz:5 0 0>

procedure

(+vy v dy)  Loc

  v : Vec
  dy : Real
Returns a vector in the coordinate space of vector v defined by adding to vector v the translation vector defined by the Cartesian coordinates 0, dy, and 0.

Example:
> (+vy (vxyz 1 2 3) 5)

#<vxyz:1 7 3>

procedure

(+vz v dz)  Loc

  v : Vec
  dz : Real
Returns a vector in the coordinate space of vector v defined by adding to vector v the translation vector defined by the Cartesian coordinates 0, 0, and dz.

Example:
> (+vz (vxyz 1 2 3) 3)

#<vxyz:1 2 6>

procedure

(+vxy v dx dy)  Loc

  v : Vec
  dx : Real
  dy : Real
Returns a vector in the coordinate space of vector v defined by adding to vector v the translation vector defined by the Cartesian coordinates dx, dy, and 0.

Example:
> (+vxy (vxyz 1 2 3) 1 3)

#<vxyz:2 5 3>

procedure

(+vxz v dx dz)  Loc

  v : Vec
  dx : Real
  dz : Real
Returns a vector in the coordinate space of vector v defined by adding to vector v the translation vector defined by the Cartesian coordinates dx, 0, and dz.

Example:
> (+vxz (vxyz 1 2 3) 1 5)

#<vxyz:2 2 8>

procedure

(+vyz v dy dz)  Loc

  v : Vec
  dy : Real
  dz : Real
Returns a vector in the coordinate space of vector v defined by adding to vector v the translation vector defined by the Cartesian coordinates 0, dy, and dz.

Example:
> (+vyz (vxyz 2 1 5) 1 2)

#<vxyz:2 2 7>

procedure

(vpol rho phi [cs])  Loc

  rho : Real
  phi : Real
  cs : Cs = (current-cs)
Returns a vector in the coordinate space cs defined by the polar coordinates rho and phi, corresponding to the cylindrical coordinates rho, phi, and 0.

Example:
> (vpol 1 0)

#<vxyz:1 0 0>

> (vpol (sqrt 2) (/ pi 4))

#<vxyz:1.0000000000000002 1.0 0>

> (vpol 1 (/ pi 2))

#<vxyz:6.123233995736766e-17 1.0 0>

> (vpol 1 pi)

#<vxyz:-1.0 1.2246467991473532e-16 0>

procedure

(pol-rho v)  Real

  v : Vec
Returns the first polar coordinate of vector v.

Example:
> (pol-rho (vpol 10 (/ pi 3)))

10.0

> (pol-rho (vxyz 1 2 3))

2.23606797749979

procedure

(pol-phi v)  Real

  v : Vec
Returns the second polar coordinate of vector v.

Example:
> (pol-phi (vpol 10 (/ pi 3)))

1.0471975511965976

> (pol-phi (vxyz 1 2 3))

1.1071487177940904

procedure

(+vpol v drho dphi)  Loc

  v : Vec
  drho : Real
  dphi : Real
Returns a vector in the coordinate space of vector v defined by adding to vector v the translation vector defined by the polar coordinates drho and dphi.

Example:
> (+vpol (vxy 1 2) (sqrt 2) (/ pi 4))

#<vxyz:2.0 3.0 0>

> (+vpol (vxy 1 2) 1 0)

#<vxyz:2 2 0>

> (+vpol (vxy 1 2) 1 (/ pi 2))

#<vxyz:1.0 3.0 0>

procedure

(vcyl rho phi z [cs])  Loc

  rho : Real
  phi : Real
  z : Real
  cs : Cs = (current-cs)
Returns a vector in the coordinate space cs defined by the cylindrical coordinates rho, phi, and z.

Example:
> (vcyl 5 0 10)

#<vxyz:5 0 10>

procedure

(+vcyl v drho dphi dz)  Loc

  v : Vec
  drho : Real
  dphi : Real
  dz : Real
Returns a vector in the coordinate space of vector v defined by adding to vector v the translation vector defined by the cylindrical coordinates drho, dphi, and dz.

Example:
> (+vcyl (vxyz 1 2 3) 3 pi/6 5)

#<vxyz:3.598076211353316 3.5 8>

procedure

(vsph rho phi psi [cs])  Loc

  rho : Real
  phi : Real
  psi : Real
  cs : Cs = (current-cs)
Returns a vector in the coordinate space cs defined by the spherical coordinates rho, phi, and psi.

Example:
> (vsph (sqrt 3) pi/4 (atan (sqrt 2)))

#<vxyz:1.0 0.9999999999999999 0.9999999999999999>

procedure

(+vsph v drho dphi dpsi)  Loc

  v : Vec
  drho : Real
  dphi : Real
  dpsi : Real
Returns a vector in the coordinate space of vector v defined by adding to vector v the translation vector defined by the spherical coordinates drho, dphi, and dpsi.

Example:
> (+vsph (vxyz 1 2 3) 4 pi/4 pi/3)

#<vxyz:3.4494897427831783 4.449489742783178 5.0>

procedure

(u-vxyz x y z [cs])  Vec

  x : Real
  y : Real
  z : Real
  cs : Cs = (current-cs)
Returns a vector in the coordinate space cs that has the same direction as the vector (x, y, z) but with magnitude 1.

Example:
> (u-vxyz 1 2 3)

#<vxyz:0.2672612419124244 0.5345224838248488 0.8017837257372732>

> (u-vxyz 2 2 2)

#<vxyz:0.5773502691896258 0.5773502691896258 0.5773502691896258>

procedure

(unitize v)  Vec

  v : Vec
Returns a vector in the coordinate space of v that has the same direction as the vector v but with magnitude 1.

> (unitize (vxyz 1 2 3))

#<vxyz:0.2672612419124244 0.5345224838248488 0.8017837257372732>

> (unitize (vpol 2 pi/4))

#<vxyz:0.7071067811865476 0.7071067811865475 0>

12.5 Operations with Locations

procedure

(distance p1 p2)  Real

  p1 : Loc
  p2 : Loc
Returns the distance between locations p1 and p2.

Example:
> (distance (xyz 0 0 0) (xyz 2 4 6))

7.483314773547883

> (distance (pol 2 0) (sph 5 pi/2 pi/3))

5.385164807134504

12.6 Algebraic Operations with Locations and Vectors

procedure

(p-p p q)  Vec

  p : Loc
  q : Loc
Returns the translation vector from p to q.

Example:
> (p-p (xyz 1 1 1) (xyz 2 2 2))

#<vxyz:-1 -1 -1>

procedure

(p+v p v)  Loc

  p : Loc
  v : Vec
Returns the location that results from applying the translation described by vector v to the location p.

Example:
> (p+v (xyz 1 1 1) (vxyz 2 2 2))

#<xyz:3 3 3>

procedure

(v+v v1 v2)  Vec

  v1 : Vec
  v2 : Vec
Returns the location that results from applying the translation described by vector v to the location p.

Example:
> (v+v (vxyz 1 1 1) (vxyz 2 2 2))

#<vxyz:3 3 3>

procedure

(v*r v a)  Vec

  v : Vec
  a : Real
Returns the product between the vector v and the scalar a.

Example:
> (v*r (vxyz 1 2 3) 2)

#<vxyz:2 4 6>

procedure

(v/r v a)  Vec

  v : Vec
  a : Real
Returns the division between the vector v and the scalar a.

Example:
> (v/r (vxyz 2 4 6) 2)

#<vxyz:1 2 3>

procedure

(u0 [cs])  Loc

  cs : Cs = (current-cs)
Returns the origin of the coordinate space cs.

Example:
> (u0)

#<xyz:0 0 0>

12.7 1D Modeling

procedure

(point p)  Shape

  p : Loc
Creates a point at the position specified by p.

Example:
> (point (xyz  1 1 5))

#<point 19624>

12.8 2D Modeling

procedure

(circle [center radius])  Shape

  center : Loc = (u0)
  radius : Real = 1
Creates a circle specified by its centre center and radius radius.

Example:
> (circle (xyz 0 0 0) 5)

#<circle 19625>

procedure

(surface-circle [center radius])  Shape

  center : Loc = (u0)
  radius : Real = 1
Creates a surface delimited by a circle specified by its centre center and radius radius.

Example:
> (surface-circle (xyz 0 0 0) 5)

#<surface-circle 19626>

procedure

(arc [center radius start-angle amplitude])  Shape

  center : Loc = (u0)
  radius : Real = 1
  start-angle : Real = 0
  amplitude : Real = pi
Creates a circular arc specified by its centre center, radius radius, initial angle start-angle and angle amplitude amplitude.

Example:
> (arc (xyz 0 0 0) 1)

#<arc 19627>

> (arc (xyz 0 0 0) 2 pi/4 (- pi pi/4))

#<arc 19628>

procedure

(surface-arc [center    
  radius    
  start-angle    
  amplitude])  Shape
  center : Loc = (u0)
  radius : Real = 1
  start-angle : Real = 0
  amplitude : Real = pi
Creates a surface delimited by a circular arc specified by its centre center, radius radius, initial angle start-angle and angle amplitude amplitude.

Example:
> (surface-arc (xy 0 0) 10 pi/4 (* 2 pi/4))

#<surface-arc 19629>

procedure

(ellipse [center radius-x radius-y])  Shape

  center : Loc = (u0)
  radius-x : Real = 1
  radius-y : Real = 1
Creates an ellipse specified by its centre center, major radius radius-x and minor radius radius-y.

Example:
> (ellipse (xyz 0 0 0) 5 2)

ellipse: undefined;

 cannot reference an identifier before its definition

procedure

(line pt ...)  Shape

  pt : Loc
Creates an open polygonal line defined by a set of locations, specified individually or in the form of a list.

Example:
> (line (xy 0 0) (xy 5 0) (xy 5 5) (xy 0 5) (xy 0 0))

#<line 19630>

> (line (list (xy 0 0) (xy 5 0) (xy 5 5) (xy 0 5) (xy 0 0)))

#<line 19631>

procedure

(closed-line pt ...)  Shape

  pt : Loc
Creates a closed polygonal line defined by a set of locations, specified individually or in the form of a list.

procedure

(polygon pt ...)  Shape

  pt : Loc
Creates a closed polygonal line defined by a set of locations, specified individually or in the form of a list.

Example:
> (closed-line (xy 0 0) (xy 5 0) (xy 5 5) (xy 0 5))

#<closed-line 19632>

> (polygon (pol 1 (* 2 pi 0)) (pol 1 (* 2 pi 1/5))
           (pol 1 (* 2 pi 2/5)) (pol 1 (* 2 pi 3/5))
           (pol 1 (* 2 pi 4/5)))

#<polygon 19633>

procedure

(surface-polygon pt ...)  Shape

  pt : Loc
Creates a surface delimited by a closed polygonal line defined by a set of locations, specified individually or in the form of a list.

Example:
> (surface-polygon (x 0) (x 1) (xy 1 1) (y 1))

surface-polygon: undefined;

 cannot reference an identifier before its definition

procedure

(spline pt ...)  Shape

  pt : Loc
Creates an open spline interpolating a set of locations. The locations can be provided individually or in the form of a list.

Example:
> (spline (xy 0 0) (xy 5 0) (xy 5 5) (xy 0 5) (xy 0 0))

#<spline 19634>

procedure

(closed-spline pt ...)  Shape

  pt : Loc
Creates a closed spline interpolating a set of given locations. The locations can be provided individually or in the form of a list of coordinates.

Example:
> (closed-spline (xy 0 0) (xy 5 0) (xy 5 5) (xy 0 5) (xy 0 0))

#<closed-spline 19635>

procedure

(surface-polygon pt ...)  Shape

  pt : Loc

procedure

(rectangle [p0] p1)  Shape

  p0 : Loc = (u0)
  p1 : Loc
Creates a rectangle specified by the bottom-left corner p0 and top-right corners p1.

procedure

(rectangle [c] dx dy)  Shape

  c : Loc = (u0)
  dx : Real
  dy : Real
Creates a rectangle specified by the bottom-left corners c and the length dx and width dy.

Example:
> (rectangle (xy 0 0) (xy 1 1))

#<rectangle 19636>

> (rectangle (xy 0 0) 1 1)

#<rectangle 19637>

procedure

(surface-rectangle [p0] p1)  Shape

  p0 : Loc = (u0)
  p1 : Loc
Creates a surface delimited by a rectangle specified by the bottom-left corner p0 and top-right corners p1.

procedure

(surface-rectangle [c] dx dy)  Shape

  c : Loc = (u0)
  dx : Real
  dy : Real
Creates a surface delimited by a rectangle specified by the bottom-left corners c and the length dx and width dy.

Example:
> (surface-rectangle (xy 0 0) (xy 10 5))

#<surface-rectangle 19638>

> (surface-rectangle (xy 0 0) 10 5)

#<surface-rectangle 19639>

procedure

(regular-polygon [edges    
  center    
  radius    
  angle    
  inscribed?])  Shape
  edges : Integer = 3
  center : Loc = (u0)
  radius : Real = 1
  angle : Real = 0
  inscribed? : Boolean = #f
Creates a regular polygon defined by the numbers of sides edges, the centre center, the radius radius, the initial angle with the X axis angle and a Boolean value inscribed? to specify if the polygon is inscribed (#t) or circumscribed (#f).

Example:
> (regular-polygon 5 (xy 0 0) 10 0 #t)

regular-polygon: arity mismatch;

 the expected number of arguments does not match the given

number

  expected: 4

  given: 5

  arguments...:

   5

   #<xyz:0 0 0>

   10

   0

   #t

procedure

(regular-polygon [edges    
  center    
  radius    
  angle    
  inscribed?])  Shape
  edges : Integer = 3
  center : Loc = (u0)
  radius : Real = 1
  angle : Real = 0
  inscribed? : Boolean = #f
Creates a surface delimited by a regular polygon defined by the numbers of sides edges, the centre center, the radius radius, the initial angle with the X axis angle and a Boolean value inscribed? to specify if the polygon is inscribed (#t) or circumscribed (#f). If the radius value is 0 the produced result will be a point.

Example:
> (surface-regular-polygon 6 (xyz 0 0 0) 1.0 0 #t)

surface-regular-polygon: undefined;

 cannot reference an identifier before its definition

procedure

(text [str p h])  Shape

  str : String = ""
  p : Loc = (u0)
  h : Real = 1
Creates text from the specified string str, insertion location p and text height h. For parameterizing the text string use, e.g., the format function.

Example:
> (text "This is a piece of text.")

#<text 19640>

> (text "This is another piece of text." (xy 2 2) 5)

#<text 19641>

> (text (format "How much is 1+2 ?: ~A" (+ 1 2))
          (xy 2 2) 5)

#<text 19642>

> (text (format "John asked: ~A" "\"What is your name?\"")
          (xy 2 2) 5)

#<text 19643>

procedure

(text-centered [str p h])  Shape

  str : String = ""
  p : Loc = (u0)
  h : Real = 1
Creates text from the specified string str, centered around the insertion location p and text height h. For parameterizing the text string use, e.g., the format function.

Example:
> (text-centered "This is a piece of centered text.")

#<text-centered 19644>

> (text-centered "This is another piece of centered text."
                   (xy 2 2) 5)

#<text-centered 19645>

> (text-centered (format "How much is 1+2 ?: ~A" (+ 1 2))
                   (xy 2 2) 5)

#<text-centered 19646>

> (text-centered
     (format "John asked: ~A" "\"What is your name?\"")
     (xy 2 2) 5)

#<text-centered 19647>

12.9 3D Modeling

procedure

(box [p q])  Shape

  p : Loc = (u0)
  q : Loc = (xyz 1 1 1)
Creates a box specified by two opposite corners p and q.

procedure

(box [c dx dy dz])  Shape

  c : Loc = (u0)
  dx : Real = 1
  dy : Real = dx
  dz : Real = dy
Creates a box specified by the base insertion location c, length dx, width dy, and height dz.

Example:
> (box (xyz 0 0 0) (xyz 10 2 5))

box: arity mismatch;

 the expected number of arguments does not match the given

number

  expected: 1

  given: 2

  arguments...:

   #<xyz:0 0 0>

   #<xyz:10 2 5>

> (box (xyz 0 0 0) 10 2 5)

box: arity mismatch;

 the expected number of arguments does not match the given

number

  expected: 1

  given: 4

  arguments...:

   #<xyz:0 0 0>

   10

   2

   5

> (box (xyz 0 0 0) 10 5)

box: arity mismatch;

 the expected number of arguments does not match the given

number

  expected: 1

  given: 3

  arguments...:

   #<xyz:0 0 0>

   10

   5

> (box (xyz 0 0 0) 10)

box: arity mismatch;

 the expected number of arguments does not match the given

number

  expected: 1

  given: 2

  arguments...:

   #<xyz:0 0 0>

   10

procedure

(cone [cb rb ct])  Shape

  cb : Loc = (u0)
  rb : Real = 1
  ct : Loc = (+z cb 1)
Creates a cone specified by the base centre cb, base radius rb, and apex ct.

procedure

(cone [c rb h])  Shape

  c : Loc = (u0)
  rb : Real = 1
  h : Real = 1
Creates a cone specified by the base centre cb, base radius rb, and height h.

Example:
> (cone (xyz 0 0 0) 5 10)

cone: undefined;

 cannot reference an identifier before its definition

> (cone (xyz 0 0 0) 5 (xyz 1 3 10))

cone: undefined;

 cannot reference an identifier before its definition

procedure

(cone-frustum [cb rb ct rt])  Shape

  cb : Loc = (u0)
  rb : Real = 1
  ct : Loc = (+z cb 1)
  rt : Real = 1
Creates a truncated cone specified by the base centre cb, base radius rb, apex ct, and top radius rt.

procedure

(cone-frustum [c r h rt])  Shape

  c : Loc = (u0)
  r : Real = 1
  h : Real = 1
  rt : Real = 1
Creates a truncated cone specified by the base centre cb, base radius rb, height h, and top radius rt.

Example:
> (cone-frustum (xyz 0 0 0) 5 10 3)

cone-frustum: undefined;

 cannot reference an identifier before its definition

> (cone-frustum (xyz 0 0 0) 5 (xyz 1 3 10) 3)

cone-frustum: undefined;

 cannot reference an identifier before its definition

procedure

(cylinder [cb r ct])  Shape

  cb : Loc = (u0)
  r : Real = 1
  ct : Loc = (+z cb 1)
Creates a cylinder specified by the base centre cb, base radius r, and top centre ct.

procedure

(cylinder [c r h])  Shape

  c : Loc = (u0)
  r : Real = 1
  h : Real = 1
Creates a cylinder specified by the base centre c, base radius r, and height h.

Example:
> (cylinder (xyz 0 0 0) 5 10)

cylinder: undefined;

 cannot reference an identifier before its definition

> (cylinder (xyz 0 0 0) 5 (xyz 1 3 10))

cylinder: undefined;

 cannot reference an identifier before its definition

procedure

(cuboid [b0 b1 b2 b3 t0 t1 t2 t3])  Shape

  b0 : Loc = (u0)
  b1 : Loc = (+x b0 1)
  b2 : Loc = (+y b1 1)
  b3 : Loc = (+y b0 1)
  t0 : Loc = (+z b0 1)
  t1 : Loc = (+x t0 1)
  t2 : Loc = (+y t1 1)
  t3 : Loc = (+y t0 1)
Creates a cuboid with base vertices b0, b1, b2, and b3, and top vertices t0, t1, t2, and t3. If the cuboid faces are not planar, the result is undefined.

Example:
> (cuboid)

cuboid: undefined;

 cannot reference an identifier before its definition

> (cuboid (xyz 1 2 3) (xyz 3 5 3))

cuboid: undefined;

 cannot reference an identifier before its definition

procedure

(irregular-pyramid [cbs ct])  Shape

  cbs : Loc = (list (ux) (uy) (uxy))
  ct : Loc = (uz)
Creates an irregular pyramid specified by the base vertices cbs and the top vertex ct.

Example:
> (irregular-pyramid)

irregular-pyramid: undefined;

 cannot reference an identifier before its definition

> (irregular-pyramid (list (xy 0 0) (xy 1 0) (xy 2 1) (xy 1 2)) (xyz 1 2 3))

irregular-pyramid: undefined;

 cannot reference an identifier before its definition

procedure

(regular-pyramid [edges    
  cb    
  rb    
  angle    
  ct    
  inscribed?])  Shape
  edges : Integer = 4
  cb : Loc = (u0)
  rb : Real = 1
  angle : Real = 0
  ct : Loc = (+z cb 1)
  inscribed? : Boolean = #f
Creates a regular pyramid specified by the number of edges (or vertices) edges, inscribed (if inscribed?) or circuncribed (if not inscribed?) in an imaginary cone with base centre cb, base radius rb, and apex ct. The pyramid has a rotation of angle around its axis.

procedure

(regular-pyramid [edges cb rb angle] h)  1

  edges : Integer = 4
  cb : Loc = (u0)
  rb : Real = 1
  angle : Real = 0
  h : Real
[inscribed? Boolean #f]) Shape]{ Creates a regular pyramid specified by the number of edges (or vertices) edges, inscribed (if inscribed?) or circuncribed (if not inscribed?) in an imaginary cone with base centre cb, base radius rb, and height h. The pyramid has a rotation of angle around its axis.}

procedure

(regular-pyramid-frustum [edges    
  cb    
  rb    
  angle    
  ct    
  rt    
  inscribed?])  Shape
  edges : Integer = 4
  cb : Loc = (u0)
  rb : Real = 1
  angle : Real = 0
  ct : Loc = (+z cb 1)
  rt : Real = 1
  inscribed? : Boolean = #f
Creates a regular pyramid frustum specified by the number of edges (or vertices) edges, inscribed (if inscribed?) or circuncribed (if not inscribed?) in an imaginary cone frustum with base centre cb, base radius rb, top centre ct, and top radius rt. The pyramid has a rotation of angle around its axis.

procedure

(regular-pyramid-frustum [edges cb rb angle] h)  1

  edges : Integer = 4
  cb : Loc = (u0)
  rb : Real = 1
  angle : Real = 0
  h : Real
[rt Real 1] [inscribed? Boolean #f]) Shape]{ Creates a regular pyramid frustum specified by the number of edges (or vertices) edges, inscribed (if inscribed?) or circuncribed (if not inscribed?) in an imaginary cone frustum with base centre cb, base radius rb, height h, and top radius rt. The pyramid has a rotation of angle around its axis.}

procedure

(regular-prism [edges cb r angle ct inscribed?])  Shape

  edges : Integer = 4
  cb : Loc = (u0)
  r : Real = 1
  angle : Real = 0
  ct : Loc = (+z cb 1)
  inscribed? : Boolean = #f
Creates a regular prism specified by the number of edges (or vertices) edges, inscribed (if inscribed?) or circuncribed (if not inscribed?) in an imaginary cylinder with base centre cb, radius r, and top centre ct. The prism has a rotation of angle around its axis.

procedure

(regular-prism [edges cb r angle h inscribed?])  Shape

  edges : Integer = 4
  cb : Loc = (u0)
  r : Real = 1
  angle : Real = 0
  h : Real = 1
  inscribed? : Boolean = #f
Creates a regular pyramid frustum specified by the number of edges (or vertices) edges, inscribed (if inscribed?) or circuncribed (if not inscribed?) in an imaginary cone frustum with base centre cb, base radius r, and height h. The prism has a rotation of angle around its axis.

procedure

(right-cuboid [cb width height ct])  Shape

  cb : Loc = (u0)
  width : Real = 1
  height : Real = 1
  ct : Loc = 
(+z
cb 1)
Creates a right cuboid specified by the base centre cb, width width, height height and top centre ct. The cuboid will have an arbitrary rotation around its axis.

procedure

(right-cuboid [cb width height h])  Shape

  cb : Loc = (u0)
  width : Real = 1
  height : Real = 1
  h : Real = 1
Creates a right cuboid specified by the base centre cb, width width, height height and length h. The cuboid will have an arbitrary rotation around its axis.

procedure

(sphere [c r])  Shape

  c : Loc = (u0)
  r : Real = 1
Creates a sphere specified by its centre c and radius r.

Example:
> (sphere (xyz 0 0 0) 10)

sphere: undefined;

 cannot reference an identifier before its definition

Constants and Variables

value

pi : Real

The value of \(\pi\).

value

-pi : Real

The value of \(-\pi\).

value

2pi : Real

The value of \(2\pi\).

value

-2pi : Real

The value of \(-2\pi\).

value

3pi : Real

The value of \(3\pi\).

value

-3pi : Real

The value of \(-3\pi\).

value

4pi : Real

The value of \(4\pi\).

value

-4pi : Real

The value of \(-4\pi\).

value

pi/2 : Real

The value of \(\frac{\pi}{2}\).

value

-pi/2 : Real

The value of \(-\frac{\pi}{2}\).

value

pi/3 : Real

The value of \(\frac{\pi}{3}\).

value

-pi/3 : Real

The value of \(-\frac{\pi}{3}\).

value

pi/4 : Real

The value of \(\frac{\pi}{4}\).

value

-pi/4 : Real

The value of \(-\frac{\pi}{4}\).

value

pi/5 : Real

The value of \(\frac{\pi}{5}\).

value

-pi/5 : Real

The value of \(-\frac{\pi}{5}\).

value

pi/6 : Real

The value of \(\frac{\pi}{6}\).

value

-pi/6 : Real

The value of \(-\frac{\pi}{6}\).

value

3pi/2 : Real

The value of \(\frac{3\pi}{2}\).

value

-3pi/2 : Real

The value of \(-\frac{3\pi}{2}\).

Example
> pi

3.141592653589793

> -pi

-3.141592653589793

> 2pi

6.283185307179586

> -2pi

-6.283185307179586

> 3pi

9.42477796076938

> -3pi

-9.42477796076938

> pi/2

1.5707963267948966

> -pi/2

-1.5707963267948966

> pi/3

1.0471975511965976

> -pi/3

-1.0471975511965976

12.10 Randomness

procedure

(random k)  Real

  k : Real
When called with an integer argument k, returns a random exact integer in the range 0 to k-1. When called with zero arguments, returns a pseudo random inexact number between 0 and 1, exclusive.

Example:
> (random 10)

9

> (random 10)

2

> (random 1.0)

0.5976200963359419

> (random 1.0)

0.2009591181766983

procedure

(random-range a b)  Real

  a : Real
  b : Real
Returns a pseudo random number of the same type as a that is larger or equal to a and smaller than b.

Example:
> (random-range 10 20)

11

> (random-range 0.4 1.2)

1.1566266167706933

Index

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

 

+cyl
+pol
+sph
+vcyl
+vpol
+vsph
+vx
+vxy
+vxyz
+vxz
+vy
+vyz
+vz
+x
+xy
+xyz
+xz
+y
+yz
+z
-2pi
-3pi
-3pi/2
-4pi
-pi
-pi/2
-pi/3
-pi/4
-pi/5
-pi/6
-vx
-vy
-vz
1D Modeling
2D Modeling
2pi
3D Modeling
3pi
3pi/2
4pi
Adaptive Sampling
Algebra of Shapes
Algebraic Operations with Locations and Vectors
Anonymous Functions
arc
Arithmetic in Racket
Arithmetic Predicates
Bi-dimensional Coordinates
Bi-dimensional Geometric Modelling
Bodegas Ysios
box
circle
Cissoid of Diocles
closed-line
closed-spline
Combinations
Computation of Parametric Functions
Conditional Expressions
cone
cone-frustum
Constructive Geometry
Constructive Solid Geometry
Coordinate Space
Coordinates
Creating Positions
Cs
cuboid
current-cs
Curvy Facades
cx
cy
cyl
cylinder
Cylinders, Cones, and Spheres
Cylindrical Coordinates
cz
Debugging
Debugging Recursive Programs
Defining Functions
distance
Documentation
Doric Order
Doric Temples
Drawing of Trusses
ellipse
Enumerations
Epilogue
Evaluating Combinations
Exercises 1
Exercises 10
Exercises 11
Exercises 12
Exercises 13
Exercises 14
Exercises 15
Exercises 16
Exercises 17
Exercises 18
Exercises 19
Exercises 2
Exercises 20
Exercises 21
Exercises 22
Exercises 23
Exercises 24
Exercises 25
Exercises 26
Exercises 27
Exercises 28
Exercises 29
Exercises 3
Exercises 30
Exercises 31
Exercises 32
Exercises 33
Exercises 34
Exercises 35
Exercises 36
Exercises 37
Exercises 38
Exercises 39
Exercises 4
Exercises 40
Exercises 41
Exercises 42
Exercises 43
Exercises 44
Exercises 45
Exercises 46
Exercises 47
Exercises 48
Exercises 49
Exercises 5
Exercises 50
Exercises 51
Exercises 52
Exercises 53
Exercises 54
Exercises 55
Exercises 56
Exercises 57
Exercises 6
Exercises 7
Exercises 8
Exercises 9
Extrusion Along a Path
Extrusion with Transformation
Extrusions
Fermat’s Spiral
Filtering
Gaudí’s Columns
Generation of Three-Dimensional Models
Global Variables
Graphic Representation of Pairs
Helicoid
Higher Order Functions on Lists
Higher-Order Functions
Identity Function
Indentation
Integer
Interpolation by Sections
Interpolation with Guiding
Introduction
Introduction
Ionic Order
irregular-pyramid
Lamé Curve
Language Elements
Lemniscate of Bernoulli
line
Lists
Loc
Local Variables
Locations
Logical Expressions
Logical Operators
Logical Values
Mapping
Mapping and Enumerations
Modelling
Modelling Doric Columns
Modules
Multiple Selection
Name Evaluation
Names
Numbers
Operations
Operations with Coordinates
Operations with Locations
p+v
p-p
Pairs
Parametric Representation
Parametric Surfaces
Parametrization of Geometric Figures
pi
pi/2
pi/3
pi/4
pi/5
pi/6
point
pol
pol-phi
pol-rho
Polar Coordinates
Polygon
polygon
Polygonal Lines and Splines
Precision
Predefined Functions
Predefined Solids
Predicates
Predicates on Lists
Predicates with a Variable Number of Arguments
Preface
Programming for Architecture
Programming Languages
Question 1
Question 10
Question 100
Question 101
Question 102
Question 103
Question 104
Question 105
Question 106
Question 107
Question 108
Question 109
Question 11
Question 110
Question 111
Question 112
Question 113
Question 114
Question 115
Question 116
Question 117
Question 118
Question 119
Question 12
Question 120
Question 121
Question 122
Question 123
Question 124
Question 125
Question 126
Question 127
Question 128
Question 129
Question 13
Question 130
Question 131
Question 132
Question 133
Question 134
Question 135
Question 136
Question 137
Question 138
Question 139
Question 14
Question 140
Question 141
Question 142
Question 143
Question 144
Question 145
Question 146
Question 147
Question 148
Question 149
Question 15
Question 150
Question 151
Question 152
Question 153
Question 154
Question 155
Question 156
Question 157
Question 158
Question 159
Question 16
Question 160
Question 161
Question 162
Question 163
Question 164
Question 165
Question 166
Question 167
Question 168
Question 169
Question 17
Question 170
Question 171
Question 172
Question 173
Question 174
Question 175
Question 176
Question 177
Question 178
Question 179
Question 18
Question 180
Question 181
Question 182
Question 183
Question 184
Question 185
Question 186
Question 187
Question 188
Question 189
Question 19
Question 190
Question 191
Question 192
Question 193
Question 194
Question 195
Question 196
Question 197
Question 198
Question 199
Question 2
Question 20
Question 200
Question 201
Question 202
Question 203
Question 204
Question 205
Question 206
Question 207
Question 208
Question 209
Question 21
Question 210
Question 211
Question 212
Question 213
Question 214
Question 215
Question 216
Question 217
Question 218
Question 219
Question 22
Question 220
Question 221
Question 222
Question 223
Question 224
Question 225
Question 226
Question 227
Question 228
Question 229
Question 23
Question 230
Question 231
Question 232
Question 233
Question 234
Question 235
Question 236
Question 237
Question 24
Question 25
Question 26
Question 27
Question 28
Question 29
Question 3
Question 30
Question 31
Question 32
Question 33
Question 34
Question 35
Question 36
Question 37
Question 38
Question 39
Question 4
Question 40
Question 41
Question 42
Question 43
Question 44
Question 45
Question 46
Question 47
Question 48
Question 49
Question 5
Question 50
Question 51
Question 52
Question 53
Question 54
Question 55
Question 56
Question 57
Question 58
Question 59
Question 6
Question 60
Question 61
Question 62
Question 63
Question 64
Question 65
Question 66
Question 67
Question 68
Question 69
Question 7
Question 70
Question 71
Question 72
Question 73
Question 74
Question 75
Question 76
Question 77
Question 78
Question 79
Question 8
Question 80
Question 81
Question 82
Question 83
Question 84
Question 85
Question 86
Question 87
Question 88
Question 89
Question 9
Question 90
Question 91
Question 92
Question 93
Question 94
Question 95
Question 96
Question 97
Question 98
Question 99
random
Random Choices
Random Fractional Numbers
Random Numbers
Random Numbers within a Range
random-range
Randomness
Real
Recognizers
rectangle
Recursion
Recursion in Architecture
Recursion in Lists
Recursion in Nature
Recursive Types
Reduction
Reflection
Regular Polygons
Regular Stars
regular-polygon
regular-prism
regular-pyramid
regular-pyramid-frustum
Revolutions
right-cuboid
rosetta/tikz
Rotation
Rounding errors
Scale
Sections Interpolation
Selection
Semantic Errors
Sequencing
Shape
Shells
Side Effects
Simple Extrusion
Slice of Regions
Solids of Revolution
Spatial Trusses
sph
sphere
Spherical Coordinates
spline
Spring
State
Strings
Structures
Surface Normals
Surface Processing
surface-arc
surface-circle
surface-polygon
surface-rectangle
Surfaces
Surfaces of Revolution
Syntactic Errors
Syntax and Semantics of Racket
Syntax, Semantics and Pragmatics
text
text-centered
The Composition Function
The Evaluator
The Function Restriction
The Möbius Strip
The Racket Language
The Sydney Opera House
Three-dimensional Modelling
Transformations
Translation
Trefoils, Quatrefoils and Other Foils
Trusses
Types
u-vxyz
u0
unitize
Urban Planning
v*r
v+v
v/r
vcyl
Vec
Vectors
Vitruvian Proportions
vpol
vsph
vx
vxy
vxyz
vxz
vy
vyz
vz
with-cs
world-cs
x
xy
xyz
xz
y
yz
z