Notes on Mathematics for 2D and 3D Graphics
Jonathan G. Campbell
Department of Computing,
Letterkenny Institute of Technology,
Co. Donegal, Ireland.
email: jonathan dot campbell (at) gmail.com, [email protected]
URL:http://www.jgcampbell.com/msc2d3d/grmaths.pdf
Report No: jc/08/0001/r
Revision 2.3 (recompiled with tags / links)
Revision 2.4 (new App.B since 2.2)
Revision 2.5 (transformations bwteen frames added to chapter 7)
19th August 2008
Contents
1 Introduction 1
1.1 Scope and Objective . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Understanding Mathematics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.3 Recommended Additional Reading . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.4 Web and Other Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.5 Outline of the Document . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2 Coordinate Geometry and Trigonometry 1
2.1 Coordinate Geometry . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
2.2 Trigonometry . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.2.1 Sin, cos, coordinates and circles . . . . . . . . . . . . . . . . . . . . . . . 5
2.2.2 Sine and cosine functions . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.2.3 Inverse sin, cos, tan . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3 Vectors 1
3.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
3.2 Vector Algebra . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
3.3 Vector Basis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
3.3.1 Components and vector basis . . . . . . . . . . . . . . . . . . . . . . . . 2
3.3.2 Vector algebra using components . . . . . . . . . . . . . . . . . . . . . . 4
3.3.3 Linear Independence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
3.4 Scalar (dot) product . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
3.4.1 Vector length / magnitude . . . . . . . . . . . . . . . . . . . . . . . . . . 5
3.4.2 Direction, unit vector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
3.4.3 Angle between two vectors . . . . . . . . . . . . . . . . . . . . . . . . . . 6
3.4.4 Tests for Orthogonality and Parallelism . . . . . . . . . . . . . . . . . . . 6
3.4.5 Direction Cosines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.4.6 Scalar product and projection . . . . . . . . . . . . . . . . . . . . . . . . 8
3.5 Vector (cross) Product . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.6 Vector Space . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
4 Matrices and Linear Algebra 1
4.1 Linear Simultaneous Equations . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
4.2 Matrices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
4.3 Basic Matrix Arithmetic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
4.3.1 Matrix Multiplication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
4.3.2 Multiplication by a Scalar . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
4.3.3 Addition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
4.4 Special Matrices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
4.4.1 Identity Matrix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
0–1
4.4.2 Orthogonal Matrix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
4.4.3 Diagonal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
4.4.4 Transpose of a Matrix . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
4.5 Inverse Matrix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
4.6 Octave Matrix Calculator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
5 Vector Transformations 1
5.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
5.2 Linear Transformations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
5.3 Matrices as Linear Transformations . . . . . . . . . . . . . . . . . . . . . . . . . 3
5.4 Points and Vectors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
5.5 Anatomy of a Linear Transformation . . . . . . . . . . . . . . . . . . . . . . . . . 5
5.6 Examples of Linear Transformations . . . . . . . . . . . . . . . . . . . . . . . . . 9
5.6.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
5.6.2 Scaling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
5.6.3 Rotation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
5.6.4 Shear . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
5.6.5 Reflection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
5.6.6 Reflection as Negative Scaling . . . . . . . . . . . . . . . . . . . . . . . . 16
5.6.7 Projection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
5.6.8 Translation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
5.6.9 Window to Viewport Transformation . . . . . . . . . . . . . . . . . . . . 19
5.6.10 A Handy Shifting and Scaling Formula . . . . . . . . . . . . . . . . . . . . 21
5.6.11 Rigid Body Transformation . . . . . . . . . . . . . . . . . . . . . . . . . . 21
5.7 Pre-multiplication versus Post-multiplication of matrices . . . . . . . . . . . . . . 22
5.8 Memory Layout of Matrices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
5.9 Complex Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
5.9.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
5.9.2 Complex Numbers as Real Number Pairs . . . . . . . . . . . . . . . . . . 25
5.9.3 Algebra of Complex Numbers . . . . . . . . . . . . . . . . . . . . . . . . 25
5.9.4 Hamilton’s Real Number Pairs . . . . . . . . . . . . . . . . . . . . . . . . 25
5.9.5 Complex Numbers as Algebra for Euclidean Geometry . . . . . . . . . . . 26
5.9.6 Translation and Dilative Rotation . . . . . . . . . . . . . . . . . . . . . . 27
5.9.7 Algebraic Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
5.9.8 Operator Character . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
5.9.9 Link with Matrices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
6 Affine Transformations and Homogeneous Coordinates 1
6.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
6.2 Points and Vectors and Affine Spaces . . . . . . . . . . . . . . . . . . . . . . . . 2
6.3 Frames . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
6.4 Affine Transformations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
6.5 Homogeneous Coordinates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
6.5.1 Point at Infinity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
6.5.2 Vectors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
6.5.3 Points and Vectors in OpenGL . . . . . . . . . . . . . . . . . . . . . . . . 5
6.6 2D Affine Transformations in Homogeneous Coordinates . . . . . . . . . . . . . . 6
6.6.1 Translation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
6.6.2 Rotation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
0–2
6.6.3 Scaling, Shearing, Reflection . . . . . . . . . . . . . . . . . . . . . . . . . 6
6.7 Composition of Homogeneous Transformations . . . . . . . . . . . . . . . . . . . 6
7 3D Transformations 1
7.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
7.2 3D Homogeneous Coordinates . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
7.3 3D Affine Transformations in Homogeneous Coordinates . . . . . . . . . . . . . . 2
7.3.1 Translation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
7.3.2 Rotation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
7.3.3 Scaling, Shearing, Reflection . . . . . . . . . . . . . . . . . . . . . . . . . 3
7.4 Transformations Between Frames . . . . . . . . . . . . . . . . . . . . . . . . . . 3
7.4.1 Rotation about other axes . . . . . . . . . . . . . . . . . . . . . . . . . . 6
7.4.2 Include Translation between Frames . . . . . . . . . . . . . . . . . . . . . 7
7.4.3 Transformation in the opposite direction . . . . . . . . . . . . . . . . . . . 7
7.4.4 Concatenation of Transformations between Frames . . . . . . . . . . . . . 7
8 Projections 1
8.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
8.2 Eyes and Cameras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
8.3 Perspective Transformation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
8.3.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
8.3.2 Geometry of Perspective . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
8.3.3 OpenGL glFrustum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
8.3.4 Derivation of Perspective Transformation . . . . . . . . . . . . . . . . . . 6
8.3.5 Orthographic Projection . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
9 Quaternions 1
9.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
9.2 The Algebra of Quaternions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
9.2.1 Addition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
9.2.2 Multiplication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
9.2.3 Inverse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
9.2.4 Scalar Products — no inverse . . . . . . . . . . . . . . . . . . . . . . . . 4
9.2.5 Point as Quaternion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
9.2.6 Rotation Quaternion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
9.2.7 Rotation — an example . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
9.2.8 Multiplicative Absolute Value . . . . . . . . . . . . . . . . . . . . . . . . . 7
9.3 Why use Quaternions? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
9.4 Conversions to and from Quaternions etc. . . . . . . . . . . . . . . . . . . . . . . 8
9.5 Software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
A Abstract Algebra on a Set S 1
B Vectors and 2D Transformations in Java 1
B.1 Vector2DH.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
B.2 Test Program for Vector2DH.java . . . . . . . . . . . . . . . . . . . . . . . . . . 8
B.3 Transform2D.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
B.4 Test for Transform2D.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
0–3
C 3D Affine Transformations in Java 1
C.1 Homogeneous 3D Vector — Vector4D.java . . . . . . . . . . . . . . . . . . . . . 1
C.2 Test for Vector4D.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
C.3 Homogeneous 3D Vector Transformations — Transform4D.java . . . . . . . . . . 4
C.4 Test for Transform4D.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
D 3D Affine Transformations in C++ 1
D.1 Homogeneous 3D Vector — Vector4D.h . . . . . . . . . . . . . . . . . . . . . . . 1
D.2 Homogeneous 3D Vector — Vector4D.cpp . . . . . . . . . . . . . . . . . . . . . 2
D.3 Test for Vector4D.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
D.4 Homogeneous 3D Vector Transformations — Transform4D.h . . . . . . . . . . . 4
D.5 Homogeneous 3D Vector Transformations — Transform4D.cpp . . . . . . . . . . 5
D.6 Test for Transform4D.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
E Quaternions in Java 1
E.1 Quaternion class — Quaternion.java . . . . . . . . . . . . . . . . . . . . . . . . . 1
E.2 Example Program using Quaternions . . . . . . . . . . . . . . . . . . . . . . . . . 5
0–4
Chapter 1
Introduction
1.1 Scope and Objective
The objectives of these Notes on Mathematics for Computer Graphics are threefold:
• to collect in one place a set of facts and formulas that surround the theoretical basis of
two-dimensional (2D) and three-dimensional (3D) computer graphics. It is aimed at two
course: BSc in Computer Games Development module games Programming 1, and MSc in
Computer Games Development module 2D and 3D Computer Graphics. It starts at a very
elementary level; whilst we would expect students to know trigonometry, there is no harm in
having a resource that collects together the necessary ideas and formulas. Again, you will
be attending separate mathematics courses, but still it is handy for me to be able to rely on
saying, see equation xyz on page P, and know that you will have it at your fingertips;
• to provide a possible learning path for the mathematics of 2D and 3D graphics; at least to
provide a set of notes upon which I can base lectures;
• perhaps the main objective is to provide a basis from which students teach themselves more
advanced mathematics, i.e. to get them ready to read books and papers on computer
graphics, virtual reality games, and the mathematics that underlie these topics.
1.2 Understanding Mathematics
Many users of this document will start off with a fear and loathing of mathematics. On the
other hand it is hard to get through a page of a book on computer graphics or computer games
without coming across some equation or other. In addition, mathematics provides such a succinct,
expressive, and effective language for communicating many of the ideas behind graphics and games
that alternatives are almost unthinkable. What to do then?
0. For a start, attend lectures and work hard at your mathematics courses; if you turn up your
nose and decide that I’m no good at this, then that surely will remain true.
1. Realise that mathematics is only a language. Be patient and learn the basics. For sure, the
mathematical language that we use is a lot more straightforward than any programming language.
1–1
2. Give yourself a chance. Try to figure out what that difficult looking equation means. Don’t
give up immediately; don’t give up until you have tried a number of times; and even then don’t
give up — ask questions in lectures or tutorials or in practical classes.
3. In most cases a mathematical equation has associated with it
• an (abstract) equation; okay maybe that is difficult to start with;
• some numerical examples — concrete examples; maybe easier to get to grips with; and after
you’ve see a few (concrete) examples maybe the (abstract) equation will become understand-
able;
• a picture; e.g. of the graph of a function, or the result of a transformation; like numerical
examples, these can bring equations to life;
• computer programs and algorithms; maybe you think better in these ‘languages’; but they
are just another way to express equations.
So, equations have real meanings — try to find your way of grasping that meaning.
4. Don’t despair. Find out, from lecturers and others, what is really needed and what you can
avoid.
1.3 Recommended Additional Reading
A lot of the mathematics in computer graphics books is sloppy and careless; the authors feel
the need to include mathematics, but the don’t have the space to introduce it properly. Notable
exceptions are (Foley, van Dam, Feiner, Hughes & Phillips 1994) and (Foley, van Dam, Feiner &
Hughes 1990).
The books I used when writing this are chiefly (in order of greatest use): (Schneider & Eberly 2003),
(Dunn & Parberry 2002), (Hoffmann 1975/1966), (Lengyel 2004), (Vince 2001) and (vanVerth
& Bishop 2004). I could recommend purchase of any of those. Schneider and Eberly (Schneider
& Eberly 2003) build a firm foundation, but I the size of the book may deter some readers; I like
(vanVerth & Bishop 2004) and I would have used more of it had it come to hand sooner; Dunn
and Parberry (Dunn & Parberry 2002) excels for its diagrams and computer code side-by-side
with mathematics. Vince (Vince 2001) is succinct and thereby easier to read by the impatient.
Later you will note many references to Lengyel (Lengyel 2004). (Buss 2003) is nice with plenty of
graphics applications (using OpenGL).
The Geometry behind all of what we are discussing is well covered in (Brannan, Esplen & Gray
1999); the Open University origins of that book show up well because of the nice diagrams; for
more geometry, see (Coxeter 1969/1989).
For a top class general introductions to linear algebra (matrices etc.), see (Lipschutz & Lipson
1999) and (Strang 2003); see also (Spiegel 1959).
For readers who want to go (a lot) further, see the references in (Campbell 2005), (Kline 1972),
(Crowe 1985/1967). Felix Klein’s two books, (Klein 2004/1925b) and (Klein 2004/1925a), were
published in German in 1925, but, to my mind, contain some excellent explanations and insights.
1–2
1.4 Web and Other Resources
The Usenet newsgroup comp.graphics.algorithms contains a very
high level of discussion on graphics mathematics; access via
http://groups.google.com/group/comp.graphics.algorithms?lnk=lr. There
is a collection of web links containing everything including the kitchen sink at:
http://www.jgcampbell.com/links/mathgr.html.
1.5 Outline of the Document
Chapter 2 contains an elementary and introductory treatment of coordinate geometry and
trigonometry. Chapter 3 introduces vectors and vector algebra. Chapter 4 gives a very brief
treatment of matrices and linear algebra. Chapter 5 introduces vector transformations (2D in this
chapter) and their implementation by matrices; if you profiled an average virtual reality animation
or game program, you would see that a vast percentage of the time (either software or on the
graphics hardware or both) is spent on matrix implementation of transformations; there is a section
on complex numbers at the end of chapter5. Chapter 6 extends linear transformations to affine
transformations and shows how affine transformations can be made implementable by matrix mul-
tiplication — important for their implementation in graphics hardware, and also important to allow
the neat mathematics of linear transformations. Chapter 7 extends the work of chapters 5 and 6
to 3D. Chapter 8 gives a brief treatment of projective transformations and shows again how these
may be implemented by our (now) beloved matrix multiplication. Chapter 9 describes quaternions
and how they provide an effective and efficient implementation of 3D rotation that is used in most
graphics end game engines.
There are Appendices on Abstract Algebra (brief introduction of the terminology) and on software
implementations of (a) 2D vectors and 2D transformations (in Java); (b) 3D homogeneous vectors
and their transformations (in Java); (c) 3D homogeneous vectors and their transformations (in
C++).
1–3
Chapter 2
Coordinate Geometry and Trigonometry
2.1 Coordinate Geometry
Normally, but not always, we use a rectangular coordinate system like that in Figure 2.1. In two
dimensions (2D), coordinates are distances from the origin, O: horizontal distance, x , and vertical
distance, y . The term Cartesian comes from the French mathematician and philosopher Rene
Descartes (1596-1650) who is widely accepted as having introduced the rectangular coordinate way
of looking at geometric problems. Thus, he originated analytic geometry which allows geometric
problems to be solved using algebraic methods; this was good news for people who thought that
geometry depended too much on diagrams an intuition.
Figure 2.1: Rectangular (Cartesian) coordinate system.
2–1
When we move to considering three dimensions (3D), we’ll need to add a third coordinate, z . If
you wanted to put a z-axis on Figure 2.1, it would start at O and point out at you, perpendicular
to the page. In general, three dimensions, (x, y , z) are needed to specify the position of a point in
space.
Aside: Non-rectangular coordinate systems The Irish National Grid as used in Ordnance Survey
maps is rectangular, and two-dimensional. On the other hand, latitude and longitude are not
rectangular coordinates, but spherical. The coordinates are angles: latitude, angle North or South,
with zero at the equator; longitude, angle East or West, with zero at the Greenwich meridian.
Exercise. If we normally need three coordinates to specify a point in space, how can we get away
with two in the case of latitude and longitude?. Hints: (a) radius; (b) even though the Earth is
not flat (I think that’s correct?), on the surface of it we occupy a sort of 2D space.
You will come across spherical / angular coordinates often in virtual reality computer games. For
example, when panning a camera (rotating it horizontally), we talk of azimuth angle; and when
tilting it vertically, we talk of elevation angle.
Back to 2D. In Figure 2.1 we give the ‘address’ of a point as (x, y); (5, 3), x = 5, y = 3 is one
such point. X-coordinates to the left of the origin are negative; y-coordinates below the origin are
negative. Note points (−1,−2) and 3,−1.5) in the diagram.
Exercise. What are the coordinates of point P in Figure 2.1? Be careful not to mix up x , always
first, and y , always second. And please note that when we talk about rows and columns of images,
(r, c) it is the other way round; and similarly for matrices which we come to later.
Exercise. Draw Q (8, 2) on Figure 2.1.
Distances When points lie on the same horizontal line, as P and S in Figure 2.2 or on the same
vertical line, as P and T in Figure 2.2, computing the distance between them is easy; in the case
of PT you simply take dy = y3 − y1, and in the case of PS, the distance is dx = x3 − x1.
In general, however, to get the as the crow flies distance, you must combine the x and y displace-
ments. PQR is a right-angled triangle, so we can apply Pythagoras’s Theorem: the square on the
hypotenuse is equal to the sum of the squares on the other two sides. The hypotenuse is the side
opposite the right-angle, i.e. PQ. So, we have
PQ2 = PR2 +QR2, or, (2.1)
d2 = d2x + d2y , so that, (2.2)
d =√d2x + d2y . (2.3)
Gradient or Slope Another thing we might want is the gradient (or slope) of the line PQ. This
is measured as,
gpq = dy/dx = (y2 − y1)/(x2 − x1). (2.4)
The gradient of PR is (y1 − y1)/(x2 − x1) = 0/(x2 − x1) = 0 and the gradient of PT is (y3 −y1)/(x1 − x1) = (y3 − y1)/0 =∞ — so don’t try the latter on your calculator!
2–2
Figure 2.2: Distances in the Cartesian coordinate system.
Figure 2.3: Cartesian coordinate system.
2–3
Exercise. Examine Figure 2.3 and answer the questions below.
(a) Compute the distance between P and Q.
(b) Compute the distance between P and R.
(c) Compute the distance between P and S.
(d) Compute the distance between P and T (d1). This is the length of the diagonal in a square
whose sides are length 1; you should become familiar with this number d1 = ˙˙˙˙˙˙˙; also its
reciprocal 1/d1 = ˙˙˙˙˙˙˙˙˙˙.
(e) (i) Compute the distance between Q and P. (ii) What in general can we say about distances
AB and BA?
(f) What are the gradients of (i) PR, (ii) PQ (don’t use a calculator!), (iii) PS, (iv) PT ?
(g) What are the gradient of (i) RP , (ii) QP (again, don’t use a calculator!), (iii) SP , (iv) TP ?
(h) If you are going up, gradient is ˙˙˙ive; if you are going down, gradient is ˙˙˙ive; if you are
going along a horizontal line, the gradient is ˙˙˙.
(j) U is at (x = −1, y = −2); mark it on the diagram. Now, (i) compute the distance UP ; (ii)
compute the distance PU; (iii) distances are always ≥ ˙˙˙ ?.
(k) What is the gradient (slope) of UP? (j) What is the gradient of PU?
Games context In a 2D game, we often need to know if two objects have come close together
— for example, a character may collect a power up by touching a power up object; or the character
may become damaged by getting close to an enemy. A very crude way to do this, i.e. compute
proximity, or collision detection, is to compare the coordinates of the character (xc , yc) with the
coordinates of the enemy (xe, ye). In Java or C++:
if(xe == xc && ye == yc) do something;
else do nothing and carry on;
This is crude and may cause the game to appear unreal because object have to be right on top
of one another for contact to be registered; what we want is proximity, i.e. compute the distance
between the two objects and if it is less than some small distance, we have contact. In Java:
smallDistance = 1.25; // for example
dx = xe - xc; dy = ye - yc;
dxSquared = dx*dx; dySquared = dy*dy;
dSquared = dxSquared + dySquared;
d = Math.sqrt(dSquared);
if(d ¡ smallDistance) do something;
else do nothing and carry on;
Another method is bounding box ; here each object is considered to have a box surrounding it and
there is contact if the boxes touch or overlap.
2–4
2.2 Trigonometry
When there is a right angle (90◦ or π/2 radians) in a triangle, the ratios of lengths of sides are
known and can be printed in books of tables and programmed into calculators. See Figure 2.4.
Figure 2.4: Trigonometry: sine, cosine and tangent.
The ratios are sine, cosine, and tangent, abbreviated, respectively: sin (spoken ‘sign’), cos, and
tan.
Thus, if you know the angle a and the length of PQ, the hypotenuse, you can compute RQ using
PR/PQ = sin a, so PR = PQ sin a.
Example. a = 30◦, PQ = 5, what is PR? A calculator or book of tables tells me that sin a = 0.5.
So, PR = PQ sin a = 5× 0.5 = 2.5. And what is RQ? Use cos. RQ/PQ = cos a; cos a = 0.866,
so RQ = PQ cos a = 5× 0.866 = 4.33.
2.2.1 Sin, cos, coordinates and circles
sin, cos, tan are often called circular functions as shown in Figure 2.5. If we have a point P on a
circle of unit radius (radius r = 1) and we rotate P anticlockwise from the x-axis by an angle a,
the x- and y-coordinates of P are cos a and sin a, respectively. This fact will crop up again and
again; if you don’t know much about sin, cos, tan, that diagram is a good way of figuring out the
relationships — much better than learning off formulas.
Note. All the important diagrams and formulas in this chapter will be given in an appendix of any
examination paper you do.
cos is cosine, meaning that it covaries with sine — as sine increases, so cosine decreases and
vice-versa.
From Figure 2.5 we can see from Pythagoras’s Theorem that
sin2 a + cos2 a = 1. (2.5)
2.2.2 Sine and cosine functions
Figure 2.6 shows graphs of cosine and sine for the angle range −π to 2π. angles.
2–5
Figure 2.5: Sin and cos and circlular movement.
Figure 2.6: Graphs of cos (left) and sin (right); angles from -PI to + 2*PI.
2–6
Some values. sin 0◦ = 0, sin 30◦ = 0.5, sin 60◦ =√
3/2 = 0.866, sin 90◦ = 1.
cos 0◦ = 1, cos 30◦ =√
3/2 = 0.866, cos 60◦ = 0.5, cos 90◦ = 0.
So, you can see that sine and cosine have the same shape, but sin a is 90◦ behind cos a as a
increases: cos a − 90◦ = sin a. You can verify this using Figure 2.5.
Radians Before we go on, we have to mention that many applications measure angles in radians;
a radian is about 57◦; it is the angle subtended by an arc of length r on a circle of radius r . Very
often you will see π/2 radians = 90◦, π radians = 180◦, 2π radians = 360◦, etc.
π = 3.141592635 . . ..
To convert degrees, d , to radians, r : r = (180/π)× d .
Cosine and sine are periodic over 360 degrees Cosine and sine repeat themselves after 2π
radians or 360◦.
Sine is odd, cosine is even Sine is an odd function: sin−a = − sin a.
Cosine is an even function: cos−a = cos a.
Armed with the latter two facts (periodic and odd / even), given cos a and sin a for a = 0 . . . π,
you can derive the cos a and sin a for any angle a.
2.2.3 Inverse sin, cos, tan
Sometimes we know that the cos of the angle is c , and we need to compute the angle? The inverse
function cos−1 gives the answer. We have also sin−1, and tan−1; like sin, cos, tan these are in books
of tables and in calculators. Other names for these inverse functions are arccos, arcsin, arctan. In
programming language mathematics libraries they have names like acos, asin, etc.
Useful equations
sin(a + b) = sin a cos b + cos a sin b, (2.6)
sin(a − b) = sin a cos b − cos a sin b, (2.7)
cos(a + b) = cos a cos b − sin a sin b, (2.8)
cos(a − b) = cos a cos b + sin a sin b. (2.9)
sin2 a + cos2 a = 1. (2.10)
sin2 a =1
2(1− cos 2a). (2.11)
2–7
cos2 a =1
2(1 + cos 2a). (2.12)
cos 2a = cos2 a − sin2 a = 1− sin2 a = 2 cos2 a − 1. (2.13)
2–8
Chapter 3
Vectors
3.1 Introduction
Roughly speaking, vectors are objects with magnitude and direction. Figure 3.1 shows vectors u, v
and other vectors derived from them. Vectors are normally shown in bold-face, e.g. u, though
other symbolisms are used, for example, underscoring, or over-scoring with arrows.
Books often emphasise vectors as being made up from a set of components, i.e. where vectors
are referenced to a set basis vectors, normally orthogonal to one another, see section 3.3. The
components give the contribution (weight) of each of the basis vectors. Actually, however, vectors
exist without any basis being specified; on the other hand, when we get to computer programs,
we do need to resort to numerical components. It is not uncommon to see the components (with
respect to a basis) referred to as coordinates; but it is best to resist that temptation, for then we
may confuse vector and point; normally it is best to make a very clear distinction between vectors
(representing movement or displacement of points) and points themselves (representing location),
see chapter 6.
Vectors do not have locations, the three versions of u are the same vector; likewise the two versions
of v.
Often components (with respect to a basis) are called coordinates but, in spite of this name, note
again that a vector has no location.
In order to distinguish vectors from real numbers (which have magnitude and sign, but no direction),
in the vector context a real number is called scalar.
3.2 Vector Algebra
The basic operations on vectors are:
(a) Addition. In Figure 3.1 this is done using either (i) the head-to-tail triangle rule or (ii) the
parallelogram rule; they are equivalent and give the same result. There is a zero vector 0 for
which u + 0 = u.
3–1
Figure 3.1: Vectors in two dimensions.
(b) Multiplication by a scalar, (scaling the vector). For example, in Figure 3.1 see the effect of
multiplying vector u by 1.5; the direction stays the same, magnitude is multiplied by 1.5.
This is vector algebra in a nutshell; for a more formal and complete account see section 3.6 or
(Schneider & Eberly 2003) or any of the textbooks mentioned in Chapter 1.
3.3 Vector Basis
3.3.1 Components and vector basis
As we have said, in a computer program we must represent vectors using numeric components
measured according to a basis (plural bases). A basis is a set of linearly independent vectors from
which any general vector may be expressed as a linear combination of members of the basis.
The vectors in Figure 3.1 are now shown in Figure 3.2 along with one possible basis, the vectors
e1 and e2. e1 and e2 are perpendicular (orthogonal) to one another and they are unit vectors.
That makes e1, e2 an orthonormal basis — orthogonal and normalised, where normalised means
reduced to unit length, see section 3.4.1.
It is possible to have a valid basis whose vectors are neither orthogonal nor unit length — they only
have to be linearly independent, see section 3.3.3 — but orthonormal bases bring great advantages.
Note that I have deliberately excluded axes (x- or y-axes or otherwise) from Figure 3.2; axes such
as x- and y-axes in section 2.1 are used to determine location; let us repeat: vectors, unlike
points, do not have a location. We have shown one version of e1, e2 making what looks like x-
and y- axes — that is only for handiness; note the other e1, e2 which are quite equivalent. Also,
3–2
Figure 3.2: Vector Basis.
note that the grid lines in Figure 3.2 are there only to show vector magnitudes and to give some
guide as to vector directions.
We can now express vectors using their components:
u = (u1 u2) = (5 1); this states that u is composed of five units of e1 (the five added together,
resulting in 5e1) and one unit of e2, i.e. u = 5e1 + 1e2.
v = (v1 v2) = (1 4), which means v = 1e1 + 4e2.
It is more correct to write u(u1 u2)t , which means that the vector is transposed ; in matrix
terminology, see section 4.2, (u1 u2) is a row vector or a 1× 2 vector, one row by two columns;
transposing makes it a column vector — a 2 × 1 vector. Unless otherwise stated, a vector on n
components is n × 1. But, often where the meaning is clear, we write u as (u1 u2) or (u1 , u2).
It is useful to stick to the neutral e1, e2, rather than mention x and y . However, more often than
not e1 will be aligned with the x-axis and e2 will be aligned with the y-axis; quite often we will end
up using u = (ux uy)t and v = (vx vy)t . Obviously, in 3D, this extends to u = (u1 u2 u3)t =
(ux uy uz)t . In many textbooks, you will see i, j, k replacing e1, e2, e3.
Components, coordinates u1, u2 etc. are components of u with respect to the basis e1, e2. As
mentioned earlier, the term coordinate is sometimes used instead of component, but this is best
avoided if we want to be sure that we are talking about vectors (no location) rather than points
(location).
3–3
3.3.2 Vector algebra using components
We can now do our basic vector operations by working on the individual components — as we
would have to do in a computer program:
(a) Add two vectors; u + v = (u1 + v1, uy + vy) = (5 + 1, 1 + 4) = (6, 5), which can be verified
by examining Figure 3.2;
(b) Multiply by a scalar; multiply each component by the scalar; example in Figure 3.2, −1u =
−u = (−5,−1);
(c) Zero vector, 0 = (0, 0).
3.3.3 Linear Independence
A set of n vectors V = {u1,u2, · · · ,un} is linearly dependent if any of the ui can be expressed as a
linear combination of the remaining vectors; that is, considering V = {u1,u2, · · · ,up} as a basis,
linearly dependence means that we have at least one redundant vector.
Formally, V = {u1,u2, · · · ,un} is linearly independent if
c1u1 + c2u2 + · · ·+ cnun = 0 (3.1)
if and only if c1 = 0, c2 = 0, · · · , cn = 0.
If the basis is linearly independent, then the representation of a vector using components (with
respect to the basis) is unique.
3.4 Scalar (dot) product
We’ve managed to add and (using × scalar −1) subtract vectors and multiply them by a scalar.
What about multiplying two vectors. Well sort of, as shown below. And we’ll cover another sort
of vector product in the next section. We say sort of because with multiplication normally comes
division, the inverse of multiplication. Neither of the two main vector products define an inverse
— there is no division.
Here we introduce scalar (or dot) product; you might see scalar product also called inner prod-
uct, but, take care, inner product is a more general term and you may come across other in-
ner products. Incidentally, if you ever want to explore the topic of vector products further, see
(Hoffmann 1975/1966), and (Crowe 1985/1967) for a history of vector analysis — which in fact
started with quaternions, see (Campbell 2005) and the references contained therein.
The scalar product is defined as
u · v = ‖u‖‖v‖ cos θ, (3.2)
where ‖ ‖ denotes magnitude and θ is the angle between u and v.
Eqn 3.2 immediately presents us with two significant problems, we know neither how to measure:
(a) angles (apart maybe from 0, 180◦, and possibly 90◦), nor (b) magnitudes. But all is not lost,
be patient.
3–4
We’re going to state some properties of scalar product and vector addition; for proofs and discus-
sion, see (Schneider & Eberly 2003), pp. 90–91.
(i) Positive definiteness. u · u ≥ 0;
(ii) Commutativity of the scalar product. u · v = v · u;
(iii) Distributivity of the scalar product over vector addition. u · (v + w) = (u · v) + (u ·w);
(iv) Distributivity of vector addition over the scalar product. (u + v) ·w = u ·w + v ·w.
Now, substitute u = u1e1 + u2e2 and v = v1e1 + v2e2 in the left hand side of eqn. 3.2
u · v = (u1e1 + u2e2) · (v1e1 + v2e2). (3.3)
Employing the two distributivity properties,
u · v = (u1e1 + u2e2) · (v1e1 + v2e2) (3.4)
= u1v1e1 · e1 + u1v2e1 · e2 + u2v1e2 · e1 + u2v2e2 · e2.
We know that the angle between e1 and e2 is 90◦ and that the angles between e1 and e1 and e2and e2 is 0 and that cos 90◦ = 0 and cos 0◦ = 1; somewhere, we also defined e1 and e2 to be unit
magnitude vectors.
Thus, e1 · e1 = 1, e2 · e2 = 1, (because cos 90◦ = 1 and e2 and e2 are unit length); also e1 · e2 = 0
and e2 · e1 = 0 (because cos 0 = 0).
Hence,
u · v = 1u1v1 + 0u1v2 + 0u2v1 + 1u2v2 = u1v1 + u2v2. (3.5)
so that, for vectors defined in terms of their components in an orthogonal basis of unit vectors
u · v = u1v1 + u2v2. (3.6)
Using an orthogonal basis of unit vectors (an orthonormal basis), we now have a method of
computing magnitude (length) and angle between vectors.
The scalar product is defined for any dimension, 2D, 3D, and indeed n-dimensional space, but we’ll
only use it in 2D and 3D.
The following subsections cover topics which are strongly related to scalar product.
3.4.1 Vector length / magnitude
In the context of vectors magnitude and length are equivalent terms.
Since the angle between a vector and itself is zero,
u · u = ‖u‖‖u‖ = ‖u‖2, (3.7)
3–5
so
‖u‖ =√
u · u, (3.8)
and u · u = u21 + u22 from eqn 3.6.
If we think of a vector as a displacement, eqn. 3.8 should be no surprise; recall distances in
section 2.1, and Pythagoras’s theorem, where, please note, the coordinate system is Cartesian, i.e.
the x- and y-axes are orthogonal and unit lengths along the axes are equal. We reemphasise that
‖u‖ =
√u21 + u22 , (3.9)
holds only for components with respect to an orthonormal basis. However, eqn 3.9 is so neat that
the orthonormal bases are almost universally used.
3.4.2 Direction, unit vector
Often we want just the direction of a vector; one way to achieve this is to normalise the vector to
unit length:
u = u/|u|. (3.10)
u has length 1.
Exercise. (a) u = (u1, u2) = (3, 4), what is the length |u|; (b) compute u = u/|u|; (c) verify that
|u| = 1, i.e. that it is properly normalised and is a unit vector; (d) draw u and use that to verify
your answer. Note, these are easy numbers, no need for a calculator.
Exercise. (a) u = (u1, u2) = (1.732, 1), what is the length |u|; (b) compute u = u/|u|; (c) verify
that |u| = 1, i.e. that it is properly normalised and is a unit vector; (d) draw u and use that to
verify your answer. Note: 1.732 =√
3 = 2 cos 30◦, 1 = 2 sin 30◦.
3.4.3 Angle between two vectors
Eqn. 3.6 allows us to compute u · v without knowing cos θ. Then we use eqn. 3.2 to obtain
cos θ = u · v/‖u‖‖v‖, (3.11)
and
θ = cos−1(u · v/‖u‖‖v‖). (3.12)
3.4.4 Tests for Orthogonality and Parallelism
Orthogonality If for any two vectors u and v, u · v = 0 and ‖u‖ 6= 0, and ‖v‖ 6= 0, then u is
orthogonal to v — they are perpendicular to one another, i.e. cos θ = 0 in eqn. 3.11, so
θ = 90◦.
Parallelism If u · v = ‖u‖‖v‖ then cos θ = 1 in eqn. 3.11, so θ = 0.
3–6
3.4.5 Direction Cosines
The components of a unit vector are its direction cosines, i.e. the cosines of the angles it makes
with the e1 and e2, respectively. This fact is shown in Figure 3.3, where u is a unit vector. Since
e1 and e2 are orthogonal; angle b = 90◦ − a; thus cos b = cos(90◦ − a) = sin a.
Alternatively, if u is not a unit vector, but has length r , then the components are multiplied by r :
r cos a, r sin a.
Note again that I’ve tried to show that Figure 3.3 has no coordinate axes. e1 and e2 are vectors;
where they meet is not an origin — remember, vectors do not have locations, except when people
draw diagrammatic representations of them!
This direction cosines insight will be a big help when we come to 2D and 3D transformations.
Figure 3.3: Direction cosines.
Figure 3.4 allows us to do some exercises with scalar products. For ease of working and to stress
a point about the projection role of scalar product, all the vectors in Figure 3.4 are unit vectors.
Exercise. Verify that u = (0.866 , 0.5), v = (0.5 , 0.866),p = (0 , 1),q = (0 , 1), t =
(0.866 ,−0.5) are all unit vectors, i.e. they have length 1.
Exercise. Verify that the components of u = (0.866 , 0.5), v = (0.5 , 0.866),p = (0 , 1),q =
(0 , 1), t = (0.866 ,−0.5) are indeed their direction cosines.
Exercise. Using eqn 3.6, compute: (a) u · v = (u1v1 + u2v2); (b) v · u = (v1u1 + v2u2); (c) using
the result of (b), what can you conclude about the relationship between u · v and v · u.
Exercise. Using eqn 3.6, compute: (a) u · q = (u1q1+ u2q2); (b) v · q; (c) v · p; (d) u · p; (e) v · t;
(f) p · q.
In this last exercise we are seeing that u · q is the projection of u onto e1.
In general, the scalar product of a vector u with a vector q gives the projection of u onto q.
3–7
Figure 3.4: Dot products.
Exercise. Projections can be onto vectors other than axes vectors. (a) What is the projection of
u onto v? Use eqn 3.6; note the dotted line in Figure 3.4 showing the projections. (b) What is
the projection of v onto u?
Exercise. (i) Using your knowledge of sin and cos, compute the angles a, b, c , e, f , g. (ii) What
is odd about f ? A. It is negative. (iii) Why is f negative? A. Remember anticlockwise is positive,
clockwise negative; (iv) b is also negative — the angle from y-axis to v.
Ans. a = 30◦; b = 30◦, −30◦, if we take sign into account; c = 30◦; e = 90◦; f = 30◦, −30◦, if
we take sign into account; g = 60◦.
Exercise. Using eqn 3.2 and the results of the previous exercise, compute: (a) u·v = ‖u‖‖v‖ cos c ;
(b) v · u; (c) using the result of (b), what can you again conclude about the relationship between
u · v and v · u.
Exercise. Using eqn 3.2, compute: (a) u · q; (b) v · q; (c) v · p; (d) u · p; (e) v · t; (f) p · q.
3.4.6 Scalar product and projection
As we have seen, the scalar product of any vector u with a unit vector q gives the projection of u
onto q. In the case of u · q, we have u projected onto the e1. q = (1 , 0) = 1.e1 + 0.e2 = e1.
Although projection seems a bit trivial here, projections are fundamental to 3D graphics: we
must project 3D vectors onto some 2D plane (so that we can display on a 2D screen); often we
project u = (ux , uy , uz) onto the x-y plane; if the projection is orthographic, we just throw away
the z-component, (ux , uy , uz) 7→ (ux , uy). If the projection is perspective, we modify the x- and
y-components according to their z value: (ux , uy , uz) 7→ (u′x , u′y).
3–8
3.5 Vector (cross) Product
Here we introduce the vector product, sometimes called cross product (cross simply because the
symbol× is used to denote it. Unlike the scalar product, section 3.4, the result of the vector product
is a vector. Also unlike the scalar product, the vector product is defined only in three dimensional
vector space. We’ll cover vector product briefly and refer interested readers to (Schneider &
Eberly 2003) pp. 92–93.
The vector product w = u× v has a direction perpendicular to both u and v and has magnitude
‖u‖‖v‖ sin θ, where θ is the angle between u and v.
As shown in Figure 3.5, the sign of the vector product depends on the sign of sin θ; Figure 3.5
shows the normal right-handed convention.
Figure 3.5: Vector product.
Figure 3.6 shows another interpretation of the vector product — as the area of the parallelogram
formed by u and v and with the direction as stated above.
Figure 3.6: Vector product.
3.6 Vector Space
When speaking of mathematical objects and operations involving them, mathematicians often use
the term space; this almost nothing to do with the space that we, and the moon and the stars,
occupy, except that we could call the latter a space of points.
We sometime speak of the space of real numbers, which is composed of (a) all the real numbers
and (b) the operations we can do on and with them to produce other real numbers.
A vector space is made up of:
3–9
(a) Vectors. The set of all vectors possible in that space; we might for example restrict to 2D
or 3D vectors;
(b) Vector addition. If you add two vectors, you get another vector. Addition is associative and
commutative.
(i) Addition is associative, i.e. if u, v,w are vectors, (u + v) + w = u + (v + w).
(ii) Addition is commutative. u + v = v + u.
(c) Zero vector. There is a zero vector 0; u + 0 = 0 + u = u;
(d) Inverse of vector addition (subtraction). For every vector u we have a vector u′ so that
u + u′ = u′ + u = 0; obviously u′ = −u;
Items (a) . . . (d) make the vectors an Abelian group, see Appendix A.
(e) A set of scalars — the real numbers, e.g. a.
(f) Multiplication of vectors by scalars, for any vector u, au is a vector;
(i) Multiplication by a scalar is distributive over vector addition: a(u + v) = au + av;
(ii) Multiplication by a scalar is distributive over scalar addition: (a + b)u = au + bu;
(iii) Multiplication of a vector by a scalar is associative with scalar-scalar multiplication:
a(bu) = (ab)u.
3–10
Chapter 4
Matrices and Linear Algebra
A good deal of virtual reality graphics involves moving objects round the virtual reality world,
resizing them, rotating them, etc. Most of these operations involve sets of simultaneous equations
like eqns. 4.1 and 4.2. The algebra used in manipulating and solving such equations is called
linear algebra. Matrix algebra is central to linear algebra. The application of matrices to graphics
and games does not become properly apparent until Chapter 5 and section 5.6.3 which derives the
transformation matrix for rotation.
Hence we must cover some linear algebra, including matrix algebra. That’s what this chapter
does — introduces matrices. Chapter 3 has introduced vector algebra. Chapter 5 describes the
application of matrix algebra to vector transformations (rotation, etc.). Finally chapter 6 on
affine transformations pulls everything together and gives us the full mathematical apparatus for
manipulating objects in 2D and 3D space.
4.1 Linear Simultaneous Equations
Eqns. 4.1 and 4.2 are a pair of linear simultaneous equations,
v1 = 3u1 + 1u2, (4.1)
v2 = 2u1 + 4u2. (4.2)
Practically, these equations could express the following:
Price of an apple = u1, price of an orange = u2 (both unknown). Person A buys 3 apples, and 1
orange and the total bill is 5c (v1). Person B buys 2 apples and 4 oranges and the total bill is 10c
(v2).
Now, what is u1, the price of apples, and u2, the price of oranges? We want to solve for the
unknowns u1, u2. Matrix algebra gives us a nice technique for solving such problems, see section 4.5,
but first well see how to solve it without matrices.
Substitute v1 = 5 and v2 = 10 into eqns. 4.1 and 4.2:
5 = 3u1 + 1u2, (4.3)
10 = 2u1 + 4u2. (4.4)
4–1
Eqn. 4.3 gives u2 = 5− 3u1, which, substituted into eqn. 4.4 gives:
10 = 2u1 + 4(5− 3u1),
10 = 2u1 + 20− 12u1,
−10 = −10u1,
u1 = 1.
Now, substitute u1 = 1 into eqn. 4.3:
5 = 3 + u2,
u2 = 2.
We have determined our unknowns u1 = 1 and u2 = 2.
Ex. Substitute u1 = 1 and u2 = 2 into eqns. 4.3 and 4.4 to check the correctness of the result.
4.2 Matrices
Eqns. 4.1 and 4.2 can be written in matrix form as
v = Au (4.5)
where A is a 2 row × 2 column matrix, A =
[3 1
2 4
], v is a one column two row matrix,
representing a tuple, v =
[v1v2
]and u is another one column two row matrix, u =
[u1u2
].
Here we are being careful to call objects like u and v tuples. We are tempted to use the term
vector for tuple, but, strictly, that would be incorrect. Vectors can be represented using tuples of
components, see section 3.3, that is the connection. But all tuples are not vectors. Generally a
tuple containing n elements is called n-tuple; 2-tuple = pair, 3-tuple = triple, etc.
Generally, a system of m equations, in n variables, u1, u2, . . . , un,
v1 = a11u1 + a12u2 · · ·+ a1nun (4.6)
v2 = a21u1 + a22u2 · · ·+ a2nun
. . .
vr = ar1u1 + ar2u2 · · ·+ arnun
. . .
vm = am1u1 + am2u2 · · ·+ amnun
can be written in matrix form as
v = Au, (4.7)
4–2
where v is an m-tuple or m × 1 matrix,
v =
v1v2.
.
vm
,u is an n-tuple or n × 1 matrix,
u =
u1u2.
.
un
,and A is an m-row × n-column matrix
A =
a11 a12 a1na21 a22 a2n.. .. .. ..
.. arc .. ..
.. .. .. ..
am1 am2 .. amn
.
That is, the matrix A is a rectangular array of numbers whose element in row r , column c is arc .
The matrix A is said to be m × n, i.e. m rows, n columns.
Eqn. 4.7 can be interpreted as the definition of a function which takes n arguments (u1, u2, . . . , un)
and returns m variables (v1, v2 . . . vm). Such a function is also called a transformation: it transforms
n-tuples of real numbers to m-tuples of real numbers.
Such equations are linear transformations because there are no terms in u2r or higher, only in
ur = u1r , and no numbers like 5 (5u0r = 5× 1 = 5).
4.3 Basic Matrix Arithmetic
4.3.1 Matrix Multiplication
We may multiply two matrices A, m × n, and B, q × p, as long as n = q. Such a multiplication
produces an m × p result. Thus,
C = A B.
m × p m × n n × p (4.8)
Method: The element at the r th row and cth column of C is the product (sum of componentwise
products) of the r th row of A with the cth column of B. There are similarities between these sum
of componentwise products and the vector componentwise scalar product of eqn. 3.6. Pictorially:
4–3
n p p
---------------- ---------- -----------
—----¿ — — — — — —
— A — — B — — = — C —
— — — — — — —
m — — — — — n — — m
---------------- — V — -----------
— —
----------
C = AB
,
A =
[a11 a12a21 a22
],
B =
[b11 b12b21 b22
],
so, the product
C =
[a11b11 + a12b21 a11b12 + a12b22a21b11 + a22b21 a21b12 + a22b22
].
Example. Consider Eqn. 4.7, v = Au. Thus the product of A(m × n) and u(n × 1) is
v1 = a11u1 + a12u2 · · ·+ a1nun, · · · vm = am1u1 + am2u2 · · ·+ amnun.
In summation notation, vr =∑c=n
c=1 arcuc .
The product is (m × n)× (n × 1) so the result is (m × 1), which checks okay, for v is (m × 1).
4.3.2 Multiplication by a Scalar
As with vectors (when represented as components), we simply multiply each component by the
scalar,
c
[a11 a12a21 a22
]=
[ca11 ca12ca21 ca22
].
4.3.3 Addition
As with vectors (when represented as components), we add componentwise,
[a11 a12a21 a22
]+
[b11 b12b21 b22
]=
[a11 + b11 a12 + b12a21 + b21 a22 + b22
].
Clearly, the matrices must be the same size, i.e. row and column dimensions must be equal.
4–4
4.4 Special Matrices
4.4.1 Identity Matrix
I =
[1 0
0 1
]i.e. produces no transformation effect. Thus, IA = A
We can define the matrix inverse as follows, if AB = I then B = A−1, see section 4.5.
4.4.2 Orthogonal Matrix
A matrix which satisfies the property:
AAt = I
i.e. the transpose of the matrix is its inverse, see section 4.5.
Another way of viewing orthogonality in matrices is:
For each row of the matrix (ar1ar2....arn), the scalar product with itself is 1, and with all other
rows, 0. I.e. ∑nc=1 arcapc = 1 for r = p,
= 0 otherwise.
4.4.3 Diagonal
Later, in section 5.6.2, we will come across a scaling matrix, so called because it scales the individual
components of a column tuple (which will be used to represent vector components) multiplied by
it.
A =
[Sx 0
0 Sy
]is diagonal, i.e. the only non-zero elements are on the diagonal.
The inverse of a diagonal matrix [a11 0
0 a22
]is [
1/a11 0
0 1/a22
]
4–5
4.4.4 Transpose of a Matrix
At, spoken ‘A-transpose’.
If
A =
[a11 a12a21 a22
]then
At =
[a11 a21a12 a22
]i.e. replace column 1 with row 1 etc.
The transpose is sometimes AT or A′.
4.5 Inverse Matrix
Only for square matrices (m = n). Consider again Eqns. 4.1 and 4.2:
v1 = 3u1 + 1u2
v2 = 2u1 + 4u2
i.e. v = Au.
A =
[3 1
2 4
].
Apply this to
x =
[1
2
],
to get
v1 = 3.1 + 1.2 = 5,
v2 = 2.1 + 4.2 = 10.
What if you know v = (5 10)t and you want to retrieve u = (u1 u2)t? In other words, can
matrices help us solve for u1, u2 as we did in section 4.1?
The answer is yes. Find the inverse of A = A−1 and then apply the inverse transformation to v,
that is, multiply v by the inverse of the matrix,
u = A−1v. (4.9)
4–6
In the case of a 2 × 2 matrix
A =
[a11 a12a21 a22
]
A−1 =1
| A |
[a22 −a12−a21 a11
](4.10)
where the determinant of the array, A, is | A |= a11a22 − a12a21
If | A |= 0, then A is not invertible, it is singular.
Inverse matrices give us the equivalent of division. If | A |= 0, attempting to find the inverse is
the equivalent to calculating 1/0.
Thus for
A =
[3 1
2 4
]we have | A |= 3× 4− 2× 1 = 10 so
A−1 = (1/10)
[4 −1
−2 3
]=
[0.4 −0.1
−0.2 0.3
]
Therefore, apply A−1 to
[5
10
]We find: A−1y =
[0.4 −0.1
−0.2 0.3
].
[5
10
]=
[5× 0.4 + 10×−0.1
5×−0.2 + 10× 0.3
]=
[1
2
]which is the answer we got in section 4.1. In fact, in section 4.1 what we did was something very
similar to how one inverts a matrix in a computer program.
4–7
4.6 Octave Matrix Calculator
Octave (a free version of Matlab) is handy for doing lengthy matrix mathematics. Octave is
available in room 2213 machines; if you want it for your own machine, Google ¡GNU Octave¿. Or,
install Cygwin.
Here is an interactive Octave session corresponding to some of the mathematics in the previous
section.
octave:1¿ A = [3, 1; 2, 4]
A =
3 1
2 4
octave:2¿ B = inv(A)
B =
0.400000 -0.100000
-0.200000 0.300000
octave:6¿ v= [5;10]
v =
5
10
octave:7¿ B*v
ans =
1.0000
2.0000
Much easier than using a calculator! If you want Octave for your personal machine search for
it using Google. Octave is a free version of Matlab; if you have Matlab, fine; but Matlab costs
gazillions per annum for a licence.
4–8
Chapter 5
Vector Transformations
5.1 Introduction
In chapter 3 we have discussed adding vectors and multiplying them by scalars. We are now going
to extend the range of operations, though all the time noting that the new operations are composed
of addition of vectors and scalar multiplication of vectors — after all that is all we can legally do
with vectors.
Before you go further, make sure you are familiar with at least the basics of matrices, see sec-
tion 4.2. Transformation of a vector all about making a new vector by altering the values of
the components that made up the old vector. Hence the relevance of simultaneous equations in
section 4.1.
Most of this chapter is taken from (Schneider & Eberly 2003) and (Dunn & Parberry 2002), with
a little from (Strang 2003). See also (Vince 2001) for a nice succinct coverage, (Lengyel 2004)
and either of the Foley-vanDam books: (Foley et al. 1994) or (Foley et al. 1990).
5.2 Linear Transformations
A vector transformation, T (u), is a function or map which maps a vector, u, (belonging to one
vector space — see section 3.6) to another vector,v, (belonging to the same vector space, or to
a different one),
T (u) 7→ v, (5.1)
where we use the 7→ deliberately to show that T takes u and makes v out of it — transforms u to
v. In the language of computer programming, T is a function, u is the input, and v is the output.
In computer graphics, normally, but not always, the vector spaces involved are normally the space
of 2D or 3D displacement vectors. The input and output vector spaces need not be the same.
If the vector spaces are U and V , we say T : U 7→ V . If we carry the analogy with computer
program functions further, we are saying that T is a function whose type is V T(U), just like the
signature/type int f1(double);.
Any linear transformation, T , preserves the two operations that involve vectors, namely vector
addition and scalar-vector multiplication (section 3.6):
5–1
(i)
T (u + v) = T (u) + T (v); (5.2)
(ii)
T (au) = aT (u); (5.3)
(iii) Consequently, combining (i) and (ii), we see that T preserves linear combinations of vectors,
T (au + bv) = aT (u) + bT (v). (5.4)
We can further see that composition of linear transformations, R = S◦T , (a linear transformation,
T , followed by another linear transformation, S) is yet again a linear transformation because. To
see this,
S(T (au + bv)) = S(aT (u) + bT (v)) = aS(T (u)) + bS(T (v)); (5.5)
that is,
R(au + bv) = aR(u) + bR(v), (5.6)
where R = S ◦ T ; eqn 5.6 shows that eqn 5.4 is satisfied by R = S ◦ T and so S ◦ T is a linear
transformation.
Corresponding to any linear transformation T , there is an inverse transformation T−1 such that
I = T ◦ T−1, (5.7)
and
I(u) = u. (5.8)
These last few facts mean that linear transformations form a group, see A, where composition, ◦,replaces + and where the identity transformation, I, replaces 0.
We note also that, like group operations, linear transformations are associative, i.e.
(T1 ◦ T2) ◦ (T3) = (T1) ◦ (T2 ◦ T3). (5.9)
What eqn. 5.9 boils down to is that we can take a vector, u and transform it v′ = T1(u) and then
transform v′ as v′′ = T2(v′) and then v′′′ = T3(v′′) and so on . . . , v(n) = Tn(v(n−1) we can replace
all this with R = T1 ◦ T2 ◦ · · · ◦ Tn, and v(n) = R(v).
Note: alternative terms for composition are concatenation (or catenation) and compounding.
Why did we go to the trouble of building up to and stating this last sentence? Simply because it is
key to much of virtual reality computer graphics. Typically, between being created by a computer
program in a virtual 3D world and being rendered onto a 2D screen for viewing, computer graphics
objects (e.g. points, triangles) undergo a sequence of transformations, T1, T2, · · · , Tn, in the so
called graphics pipeline. If we can see to it that the transformations in the sequence are all linear,
then it means that we can compose them, R = T1 ◦T2 ◦ · · · ◦Tn; the composition needs to be done
just once, and the composite R applied to each object (maybe hundreds of thousands of them). If
n = 10, say, this is a huge saving and a crucial factor in the speed of modern graphics accelerators
and systems like OpenGL and DirectX.
Figure 5.1 (Figure 3-2 of (Shreiner, Woo, Neider & Davis 2006)) shows the transformation stages
that vertexes undergo in an OpenGL rendering pipeline; DirectX differs only in minor detail.
5–2
Figure 5.1: Vertex transformation pipeline.
In Figure 5.1, object coordinates are the raw vertex coordinates that appear in an OpenGL program.
Depending of the depth of nesting of objects — think world, robot, robot-arm, arm-hand, hand-
finger, finger-joint, . . . and large number of transformations may be contained in (via composition)
the modelview matrix.
The linear in linear transformation is highly restrictive. For example, if T simply adds another
vector to the first,
T (u) = u + t, (5.10)
T is not linear. To see this, we’ll work out the two sides of eqn. 5.2. First, the left hand side,
T (u + v) = u + v + t. (5.11)
Now the right hand side,
T (u) + T (v) = u + t + v + t = u + v + 2t, (5.12)
and we have a problem because the basic requirement of linearity, eqn. 5.2, is violated.
Shock horror, linear transformations cannot do vector addition! Worse still, we know that when
we come to moving objects (their points) around in a virtual world, vector addition is how we do
it. Given what we said about the crucial advantages of linearity in the last paragraph but one, we
seem to have a problem. All is not lost, Chapter 6 will show us that there is a simple way to make
eqn. 5.10 act linear.
5.3 Matrices as Linear Transformations
If as in section 3.3, we represent vectors as components with respect to a basis, then we can use
matrices and matrix arithmetic, section 4.3.1, to implement linear transformations. To see that
this is true, let us express a vector u in terms of a weighted sum of basis vectors,
u = u1e1 + u2e2, (5.13)
and then apply eqn. 5.2,
T (u) = T (u1e1 + u2e2), (5.14)
= u1T (e1) + u2T (e2).
5–3
Now, we know, see section 3.3 that any vector can be expressed as a linear combination of the
basis vectors, and that includes transformed basis vectors such as T (e1 and T (e2. So, we can
write
T (e1) = a1e1 + b1e2, (5.15)
and
T (e2) = a2e1 + b2e2. (5.16)
So, substitute these last two equations into eqn. 5.15 to get
T (u) = u1a1e1 + u1b1e2 + u2a2e1 + u2b2e2, (5.17)
= (u1a1 + u2a2)e1 + (b1u1 + b2u2)e2.
If we name T (u) as v) = T (u), and write v as v = v1e1 + v2e2, then collecting the e1 and e2,
coefficients, we have,
v1 = a1u1 + b1u2, , (5.18)
v2 = b1u1 + b2u2.. (5.19)
Now refer back to eqns. 4.1 and 4.2 in section 4.1, and you will see the same pattern as eqns. 5.18
and 5.19. In section 4.2 we showed how to express eqns. 4.1 and 4.2 using matrices. Thus,
eqns. 5.18 and 5.19 can be written in matrix form as
v = Au. (5.20)
A is a 2 row × 2 column matrix, A =
[a1 b1a2 b2
], v is a one column two row matrix, containing
a tuple of vector components, i.e. v is a two-dimensional vector, v =
[v1v2
]and u is another a
two-dimensional vector, u =
[u1u2
].
Vectors as Matrices We note again that instead of v = (v1, v2), we are now going to apply the
convention that vectors are columns rather than rows, v =
[v1v2
].
As already mentioned, v is a 2 × 1 matrix, 2 rows by one column; again, as before, because the
column representation above can take up too many lines, we often write, v = (v1, v2)t , where t
denotes transposition or just v = (v1, v2). Transpose takes an n ×m matrix and makes it m × n;
here, we have 1× 2→ 2× 1.
5–4
5.4 Points and Vectors
In Section 2.1 we dealt with points, e.g. P = (Px , Py), and in Section 3 dealt with vectors, e.g.
v = (v1v2)t . You could be excused for confusing points and vectors. The difference is rather
subtle. When we say that P above can be represented by a vector p = (p1, p2), what we mean is
that P can be represented by the directed line (vector) OP = p, where O is the origin — a point,
P = O + p.
You can add a vector to a point to get a point. — if you get the point! We will formalise this
in chapter 6.
In the remainder of this chapter, we are going to be transforming points (P), but remember, we
are really transforming vectors OP = p.
5.5 Anatomy of a Linear Transformation
[I’m not sure whether this section should be before or after section 5.6. Maybe have a quick look
at section 5.6 and then return here.]
Recall what we are doing; we are transforming the vector u by multiplying it by the 2 × 2 matrix
A to get a new vector v.
v← Au, (5.21)
where
A =
[a11 a12a21 a22
]. (5.22)
Here I’m using the ← to emphasise what would be going on in a computer program, we are taking
an input vector, u, multiplying it by A to get a new vector which we assign to v.
We are quite aware that what really is going on, namely equations 5.23 and 5.24:
v1 ← a11u1 + a12u2, (5.23)
v2 ← a21u1 + a22u2. (5.24)
Alternatively, [vxvy
]=
[a11 a12a21 a22
]·[uxuy
]. (5.25)
What happens if we use transform the basis vector (1, 0)t using A? We get[a11 a12a21 a22
]·[
1
0
]=
[a11a21
]. (5.26)
And (0, 1)t? [a11 a12a21 a22
]·[
0
1
]=
[a12a22
]. (5.27)
5–5
So, the first column of A is the vector that results in transforming (1, 0)t , and the second column
of A the vector that results in transforming (0, 1)t .
Let us see this in action. We will use the example of a unit rectangle whose bottom left hand
corner is at the origin, see Figure 5.2. X- and y-axes are shown and grid lines are shown every 0.5
units.
Figure 5.2: 2D Rectangle. (1, 0) and (0, 1) are marked as solid points.
Scaling See section 5.6.2.
We transform using
[2 0
0 1.5
]. Sure enough, we get (1, 0) → (2, 0) and (0, 1) → (0, 1.5), see
Figure 5.3(a), just as we expected.
Next transform using
[1 0
0 2
]. We get (1, 0) → (1, 0) and (0, 1) → (0, 2), see Figure 5.3(b),
again as expected.
Rotation See section 5.6.3.
We transform using
[cos 30◦ − sin 30◦
sin 30◦ cos 30◦
]=
[0.866 −0.5
0.5 0.866
]. Sure enough, we get (1, 0) →
(0.866, 0.5) and (0, 1)→ (−0.5, 0.866), see Figure 5.4(a), just as we expected.
Next transform using
[cos 45◦ − sin 45◦
sin 45◦ cos 45◦
]=
[0.707 −0.707
0.707 0.707
].
We get (1, 0)→ (0.707, 0.707) and (0, 1)→ (−0.707, 0.707), see Figure 5.4(b), just as expected.
5–6
Figure 5.3: Scaling. (a) scale x by 2, y by 1.5; (b) scale x by 1, y by 2.
The approach we have just described can be used for either of two purposes:
• we can fully determine the components of a transformation matrix if, after the transformation,
we can somehow see how (1, 0) and (0, 1) are transformed;
• if we need to know how to construct a transformation matrix for a particular transformation
task, e.g. rotate by 30◦, we can enquire: (a) where do we want (1, 0) to go to? This gives
the first column; then, (b) where do we want (0, 1) to go to? This gives the second column.
I think you will agree that it would be possible to use this method to derive the rotation
matrices above.
5–7
Figure 5.4: Rotation. (a) 30 degrees; (b) 45 degrees.
5–8
5.6 Examples of Linear Transformations
5.6.1 Introduction
[Minor repetition in this introduction of stuff from section 5.5.]
Let us repeat eqns. 5.21 and 5.22. We are transforming the vector u by multiplying it by the 2×2
matrix A to get a new vector v,
v = Au, (5.28)
where
A =
[a11 a12a21 a22
]. (5.29)
We are quite aware that what really is going on, namely equations 5.30 and 5.31:
v1 = a11u1 + a12u2, (5.30)
v2 = a21u1 + a22u2. (5.31)
We will use the example of a simplified 2D house whose bottom left hand corner is at the origin,
see Figure 5.5. X- and y-axes are shown and grid lines are shown every 50 units (world units);
width is 150 units and height 150 units.
Figure 5.5: 2D House.
5–9
5.6.2 Scaling
Scaling is easy to think about; we want to expand or contract independently along one or both of
the axes; so, we need multiplication factors on the diagonal of the matrix as in eqn. 5.32:[vxvy
]=
[sx 0
0 sy
] [uxuy
]. (5.32)
ux is expanded (sx > 1) or contracted (sx < 1) to give vx and the same for the second component
by sy along the y axis. Eqn. 5.32 leads to eqns. 5.33 and 5.34:
vx = sxux , (5.33)
vy = syuy .. (5.34)
If you want uniform scaling, sx = sy . Figure 5.6 shows two examples of non-uniform scaling; the
original house is shown (stippled) for comparison.
Figure 5.6: Scaling. Left: sx = 2, sy = 1.5; right: sx = 0.75, sy = 2.
Exercise. Analyse and describe the scaling matrix using the methods of section 5.5.
Exercise. What is the inverse, S−1, of the scaling matrix S =
[sx 0
0 sy
]?
(a) Think. How would you cancel the multiplication of something by sx ; ditto sy ;
(b) Use the formula in section 4.5;
(c) Make sure to check your answer — that S ·S−1 = I; I is the identity matrix, see section 4.4.1.
5–10
5.6.3 Rotation
Rotation is a little more difficult; in general, both of the new components vx , vy will contain some
of ux , uy . With the assistance of Figure 5.7, we can derive the coefficients of eqns. 5.33 and 5.34.
Figure 5.7: Derivation of rotation matrix.
u = (ux , uy)t of magnitude (length) r is at an angle a with the x-axis (recall positive angles mean
anticlockwise rotation); we want to rotate u by an angle b to the x-axis, to form v = (vx , vy)t .
We note that v is at an angle (a+b) with the the x-axis, and, since rotation does not alter length,
its length is still r , so, trigonometry gives us
vx = r cos(a + b), (5.35)
vy = r sin(a + b). (5.36)
From Chapter 2, we have eqns. 2.9 and 2.7, repeated here as eqns. 5.37 and 5.38,
cos(a + b) = cos a cos b − sin a sin b, (5.37)
sin(a + b) = sin a cos b + cos a sin b. (5.38)
We can use these to expand eqns. 5.35 and 5.36 as
vx = r cos a cos b − r sin a sin b, (5.39)
vy = r sin a cos b + r cos a sin b.. (5.40)
More simple trigonometry gives us r cos a = ux and r sin a = uy ; substituting these into eqns. 5.39
and 5.40 gives the rotation equations,
vx = ux cos b − uy sin b, (5.41)
vy = uy cos b + uxa sin b. (5.42)
Hence the matrix equation is [vxvy
]=
[cos b − sin b
sin b cos b
] [uxuy
]. (5.43)
5–11
This transformation rotates u anticlockwise by the angle b about the origin O.
Figures 5.8 to 5.8 show examples of rotations for 30◦, 60◦, 90◦, 180◦, 270◦; the original house is
shown (stippled) for comparison. Rotating by 360◦ gets us back to the original.
Figure 5.8: Rotation. left: 30◦; right: 60◦.
Exercise. Analyse and describe the rotation matrix using the methods of section 5.5.
Exercise. What is the inverse, R−1, of the rotation matrix R =
[cos b − sin b
sin b cos b
]?
(a) Think. How would you cancel a rotation by angle b? Rotate by −b?
(b) Use the formula in section 4.5;
(c) Make sure to check your answer — that R ·R−1 = I; I is the identity matrix, see section 4.4.1.
Note that cos(−b) = cos b and sin(−b) = − sin b.
Exercise. Rotate the point [0.707
0.707
]anticlockwise (positive) by 45 degrees. Check your answer with a drawing.
Note: sin 45◦ = 0.707 = 1/√
2 = cos 45◦; 0.707× 0.707 = 0.5 .
Exercise. Rotate the point [0
1
]anticlockwise by 90◦. Check your answer with a drawing.
5–12
Figure 5.9: Rotation. left: 90◦; right: 180◦.
Figure 5.10: Rotation. 270◦.
5–13
Exercise. What is the effect of applying a rotation matrix twice? That is, what is
R(c) · R(d)
[uxuy
]where R(c) =
[cos c − sin c
sin c cos c
]and R(d) =
[cos d − sin d
sin d cos d
].
Just multiply R(c) ·R(d) and see what you get; there should be no need to apply the result to u.
The following formulas will be useful:
sin(a + b) = sin(a) cos(b) + cos(a) sin(b)
cos(a + b) = cos(a) cos(b)− sin(a) sin(b)
Exercise. What is the effect of applying the negative rotation, −θ, to a point that has already
been rotated by +θ. That is, what is
R(−b)R(b)
[uxuy
]?
Again, just multiply R(c) · R(d) and see what you get — see section 4.4.1.
5–14
5.6.4 Shear
Shear is not something we would do deliberately — except maybe in creating an it italic font. But
shear effects do appear in certain projections.
The following equation shears along the x axis,[vxvy
]=
[1 a
0 1
] [uxuy
], (5.44)
and this one along the y axis, [vxvy
]=
[1 0
b 1
] [uxuy
]. (5.45)
If we multiply out eqn. 5.44, we see that
vx = ux + auy
vy = uy .
Note that in this case y-coordinates are not affected, while in the eqn. 5.45, the x-coordinates are
not affected.
Figure 5.11 shows examples of shear.
Figure 5.11: Shear. Left: x-axis, a = 1.5; right: y-axis, b = 1.5.
Exercise. Analyse and describe the shearing matrices using the methods of section 5.5.
5–15
5.6.5 Reflection
The following equation reflects about the y axis (x-coordinates are negated),[vxvy
]=
[−1 0
0 1
] [uxuy
], (5.46)
and this one about the x axis (y-coordinates are negated),[vxvy
]=
[1 0
0 −1
] [uxuy
]. (5.47)
If we multiply out eqn. 5.46, we see that
vx = −ux ,vy = uy .
Figure 5.12 shows examples of reflections.
Figure 5.12: Reflection. Left: y-axis; right: x-axis.
Exercise. Analyse and describe the reflection matrices using the methods of section 5.5.
5.6.6 Reflection as Negative Scaling
If you compare eqn. 5.46 with eqn 5.32 you will see that reflection about the y-axis (eqn. 5.46) can
be expressed as a negative scaling of x , sx = −1. Likewise reflection about the x-axis (eqn. 5.47)
can be expressed as a negative scaling of y , sy = −1.
5–16
5.6.7 Projection
Projection onto x-axis: [vxvy
]=
[1 0
0 0
] [uxuy
]. (5.48)
Projection onto the y-axis: [vxvy
]=
[0 0
0 1
] [uxuy
]. (5.49)
If you compare eqn. 5.48 with eqn 5.32 you will see that projection onto the x-axis can be expressed
as a zero scaling of y , sy = 0 with unity scaling of x , sx = 1. Likewise projection onto the y-axis
(eqn. 5.49) can be expressed as a zero scaling of x , sx = 0 with unity scaling of y , sy = 1.
5.6.8 Translation
Translation (displacement, movement) is given by eqn. 5.50.[vxvy
]=
[txty
]+
[uxuy
]. (5.50)
Notice that translation does not, in this form, involve a matrix. In fact, translation is not a linear
transformation; but we would like all our graphics transformations to be linear (and implemented
with matrices)! Chapter 6 shows how translation can be made linear and implementable by a
matrix multiplication.
Figure 5.13 shows an example of translation.
5–17
Figure 5.13: Translation. x-axis by 60; y-axis by 80.
5–18
5.6.9 Window to Viewport Transformation
The window to viewport transformation is an affine transformation one that crops up a lot in
computer graphics, though often behind the scenes and away from programmer control. It takes
part of (a window ) a 2D picture in world coordinates and transforms the points in the window to
screen coordinates. Note that in this context the term window means window on the world, the
sort of view one would have looking at a view through a window. Note also that both window and
viewport are 2D entities — the world may be 3D but that 3D world has been projected onto the
window.
Let us assume you have a drawing of a house and surroundings and the drawing uses real-world
units (metres say). The drawing would use world coordinates xw , yw . Now we want to take part
of that drawing and display it on a graphics display; the part of the world we call a window —
window on the world.
In general you may want to display it as part of the screen; the part of the screen we call a viewport.
Thus, the window selects what part of the world is to be displayed.
To do the drawing on the screen we need to convert from world coordinates xw , yw to screen or
device coordinates xd , yd . This is done using the window to viewport transformation:
xd = (Vxr−Vxl )(Wxr−Wxl )
(xw −Wxl) + Vxl ,
yd =(Vyt−Vyb)(Wyt−Wyb)
(yw −Wyb) + Vyb. (5.51)
The two coordinate systems are shown in Figure 5.14.
The window to viewport transformation is an affine transformation, i.e. contains shifting (transla-
tion) and scaling. Note that the scaling may be negative, in other words, it can handle the situation
in which the world y-axis points up and the screen y-axis points down — as is the case in Java
Graphics2D. We derive a general formula for shifting an scaling in section 5.6.10 below.
Exercise. Verify that eqn. 5.51 is an affine transformation, see eqn. 6.1.
Partial answer. Multiply out eqn. 6.1,
vx = a11ux + a12uy + tx
vy = a21ux + a22uy + ty , (5.52)
and change variable names vx → xd , vy → yd , ux → xw , uy → ye.
(a) What are a11, a12, a21, a22 in eqn. 5.51?
(b) What are tx , ty in eqn. 5.51?
5–19
Figure 5.14: Window to Viewport transformation.
5–20
5.6.10 A Handy Shifting and Scaling Formula
It is a common requirement to have to have to do a simple linear mapping u 7→ v based on
u0 7→ v0, u1 7→ v1; actually, it’s affine rather than linear, but it’s linear apart from the shift
(translation). We define the mapping formula as a scaling (a) plus a shift (b).
v = au + b. (5.53)
We have two pairs of points from which to derive a, b, namely, u0 7→ v0, u1 7→ v1; from these we
have two equations,
v0 = au0 + b, and, (5.54)
v1 = au1 + b; (5.55)
Eqn. 5.54 gives b in terms of a:
b = v0 − au0; (5.56)
substituting eqn. 5.56 into eqn. 5.55 gives
v1 = au1 + v0 − au0, (5.57)
so
a =v1 − v0u1 − u0
. (5.58)
Substituting eqn. 5.58 into eqn. 5.56 gives
b = v0 − u0v1 − v0u1 − u0
; (5.59)
Eqn. 5.53 becomes
v =v1 − v0u1 − u0
u + v0 − u0v1 − v0u1 − u0
, (5.60)
which, reorganised, becomes:
v = (u − u0)v1 − v0u1 − u0
+ v0. (5.61)
Thus, (i) shift u so that u0 corresponds to zero, (ii) scale by the required range divided by the old
range, (iii) add v0 to bring zero up to v0.
5.6.11 Rigid Body Transformation
When, in a linear or affine transformation, we have a matrix corresponding to a rotation, see
eqn 5.43, including zero rotation — the identity matrix, we have what is called a rigid body
transformation — the transformation does not change the shape of the object, just the orientation
angle and position.
The key to rigid body transformations is that the matrix is special orthogonal.
Look again at eqn. 5.43 repeated here as eqn. 5.62.
5–21
[vxvy
]=
[cos b − sin b
sin b cos b
] [uxuy
], (5.62)
i.e., naming the rotation matrix R, v = Ru.
If we consider the rows of R as vectors, i.e. [cos b − sin b] and [sin b cos b], we find that the pair
of vectors and R have the following four properties:
(i) each is a unit vector; for example, the length of [cos b − sin b] is√[cos b − sin b] · [cos b − sin b] =
√cos2 b + sin2 b = 1; (we know that cos2 b+ sin2 b = 1);
and the same is true for the second row;
(ii) they are orthogonal (perpendicular to one another); [cos b − sin b]·[sin b cos b] = cos b sin b−sin b cos b = 0; so, u and v are orthogonal (perpendicular to one another), see section 3.4.4;
(iii) the determinant of R is +1; |R| = cos b cos b + (− sin b)(− sin b) = cos2 b + sin2 b = 1;
(iv) the inverse of R is its transpose, see section 4.4.4. For example, R =
[cos b − sin b
sin b cos b
], so,
R−1 =
[cos b sin b
− sin b cos b
].
Notice that reflection matrices, section 5.6.5, satisfy properties (i) and (ii), but the determinant
is -1.
5.7 Pre-multiplication versus Post-multiplication of matrices
In these notes we use pre-multiplication of vectors by matrices; thus, in eqn. 5.63, the matrix A
appears on the left. [vxvy
]=
[a11 a12a21 a22
]·[uxuy
]. (5.63)
As we know, eqn. 5.63 is matrix-speak for eqns. 5.64 and 5.65:
v1 = a11u1 + a12u2, (5.64)
v2 = a21u1 + a22u2. (5.65)
In eqn. 5.63 we are multiplying a 2× 2 matrix by a 2× 1 vector (a column vector) to get a 2× 1
vector.
Many textbooks, for example (Schneider & Eberly 2003) and (Dunn & Parberry 2002) express the
same thing using post-multiplication, i.e. they consider vectors to be row vectors and so 1× 2 or
1 × 3. In this case the matrix is on the right and, in the 2D case, matrix multiplication becomes
1 × 2 vector (a row vector) by a 2 × 2 matrix to produce a 1 × 2. When post-multiplication is
the standard, any matrix involved is the transposed version of the matrix that would be used in
pre-multiplication. The result (the individual vector components) is identical, but if you are not
aware of it, you can become very confused when switching between standards.
5–22
To check that everything is okay, let use rewrite eqn. 5.63 in post-multiplication, row vector form:
[vx vy ] = [ux uy ] ·[a11 a21a12 a22
]. (5.66)
Noting that we transposed the matrix in eqn. 5.66, we see that when we multiply out eqn. 5.66 to
eqns. 5.67 and 5.68:
v1 = a11u1 + a12u2, (5.67)
v2 = a21u1 + a22u2, (5.68)
we see that the resulting equations are identical to eqns. 5.64 and 5.65. Note, however, that the
2× 2 matrix in eqn. 5.66 is a transposed version of that in eqn. 5.63.
5.8 Memory Layout of Matrices
In section 5.7 we noted the use in many textbooks of matrix post-multiplication and consequently
of row vectors (rather that our pre-multiplication of column vectors). This may be partly due
to the fact that DirectX uses so called column major representation of matrices. According to
(vanVerth & Bishop 2004) OpenGL does the same, in spite of the OpenGL documentation which
uses pre-multiplication of column vectors and which would imply a row major representation.
This is a potential source of significant confusion. Beware. If in OpenGL we read the con-
tents of a matrix into a 16 element array using glGetDoublev, see below, we need to be care-
ful to write it out using the indexing given by i= r + c*cLen; //*. That is the matrix data
are store in memory as m[0][0], m[1][0], m[2][0], m[3][0], m[0][1], “ldots and not
m[0][0], m[0][1], ... as might naively be expected.
void matPrint(int nmat, char *msg)–
double mat[16]; int r, c; int nc= 4, nr= 4, i, cLen= 4;
glGetDoublev(nmat, mat);
printf(”%s“n”, msg); printf(”[”);
for(r= 0; r¡ nr; r++)–
printf(”(”);
for(c= 0; c¡ nc; c++)–
i= r + c*cLen; //*
printf(”%.4f”, mat[i]);
if(c == nc-1) printf(”)“n”); else printf(”, ”);
˝
˝ printf(”]“n”);˝
5.9 Complex Numbers
[This section is here because it is peripherally related to 2D transformations. It is also a little
untidy and there may be repetition.]
5–23
5.9.1 Introduction
Complex numbers arose out of determination of solutions to awkward quadratic and cubic poly-
nomial equations. They involve√−1 and as you know, there is no real number which squared
become −1; this, initially they were called imaginary numbers.
A general complex number may be represented by a pair of numbers that represent the coordinates
(x, y) of points in a plane,
z = x + iy
where i =√−1.
As such, a complex number is rather like a two-dimensional vector and, indeed, the mathematics
of complex numbers provides a complete apparatus for the manipulation of 2D vectors and pairs
of coordinates.
The modulus of the complex number (which may be interpreted as the distance between the origin
and (x, y) ) is given by:
| z | = | x + iy | =√
(x × x + y × y)
i.e. using Pythagoras’s Theorem
The angle, or argument, which may be interpreted as the angle between the line (0, 0) to (x, y)
and the x-axis, is given by:
arg z = arctan y/x
i.e. the angle whose tangent (opposite/adjacent) is y/x .
Addition of complex numbers is as follows: If
z = x + iy , w = u + iv
then
z + w = x + u + i(y + v)
A graphical interpretation of addition of complex numbers is,
– draw a line from (0,0) to (x, y),
– using (x, y) as the origin, draw a line to (u, v),
– the point reached is (x + u, y + v).
I.e. vector addition.
Multiplication of complex numbers is as follows:
If
z = x + iy , w = u + iv
5–24
then
z.w = (x + iy).(u + iv) = x.u + i .i .y .v + i .(y .u + x.v)
We use i .i = −1 (i.e.√−1√−1 = −1) This gives:
z.w = (x.u − y .v) + i .(y .u + x.v)
Note: if complex numbers have zero imaginary parts, the rules given here collapse to the rules of
normal arithmetic for real numbers.
Exercise Verify the last statement, i.e. set y , v = 0 in addition and multiplication of complex
numbers.
The complex conjugate of a complex number, c = a + ib is
c∗ = a − ib
5.9.2 Complex Numbers as Real Number Pairs
Here we follow (Eves 1965/1990a), p. 119.
5.9.3 Algebra of Complex Numbers
Addition
(a + ib) + (c + id) = (a + c) + i(b + d). (5.69)
Multiplication
(a + ib)(c + id) = ac + iad + ibc + i2bd (5.70)
= (ac − bd) + i(ad + bc).
It is easy to show that addition and multiplication are commutative and associative; also multipli-
cation is distributive over addition, see appendix A.
5.9.4 Hamilton’s Real Number Pairs
Between 1833 and 1837, while he was already on the trail of quaternions, Hamilton worked on a
representation of a complex number as a pair of reals, (a, b).
Addition
(a, b) + (c, d) = (a + c, b + d). (5.71)
5–25
Multiplication
(a, b)(c, d) = (ac − bd) + i(ad + bc). (5.72)
Embedding of Real numbers
(a, 0) + (b, 0) = (a + b, 0), (5.73)
(a, 0)(b, 0) = (ab, 0).
Units
(a, b) = (a, 0) + (0, b) (5.74)
= (a, 0) + (b, 0)(0, 1)
= a + bi ,
where i = (0, 1) and (1, 0) = 1 are units.
Eqn. 5.72 leads to
i2 = (0, 1)(0, 1) = (−1, 0) = −1. (5.75)
5.9.5 Complex Numbers as Algebra for Euclidean Geometry
Here we follow (Needham 1997), chapter 1.
Every similarity (transformation) is the composition of a scaling rotation and a translation. Note:
in most books, the term dilative rotation is used instead of scaling rotation.
Figure 5.15 shows the familiar Argand diagram representation of a complex number.
Figure 5.15: Complex number as a point on the x − y plane.
5–26
Figure 5.16: Addition – translation; multiplication – rotation + scaling.
5.9.6 Translation and Dilative Rotation
Figure 5.16 shows that complex addition provides translation and complex multiplication provides
dilative rotation.
v =| v | e ib, u =| u | e ia. (5.76)
uv =| u || v | e i(a+b), (5.77)
uv is v dilated by | u | and rotated by b.
If we apply a further dilative rotation, w =| w | e ic , we find
w(uv) =| w || u || v | e i(a+b+c). (5.78)
5.9.7 Algebraic Properties
It is easy to verify that the algebra of complex numbers has the following properties, see Appendix A.
Closure i.e. the composition of two dilative rotations is another dilative rotation;
Associativity of multiplication w(uv) = (wu)v , etc. because of the associativity of real number
addition and multiplication;
Associativity of addition ;
5–27
Commutativity of multiplication uv = vu, again because of the commutativity of real number
addition and multiplication; in operator language: U ◦ V = V ◦ U;
Commutativity of addition
Unique Inverse of Multiplication (Division)
u−1 =ux
u2x + u2y− i
uyu2x + u2y
, (5.79)
=ux − iuyu2x + u2y
,
which is defined except in the case of ux = 0, uy = 0; the inverse property is more easily seen
if | u |= 1 and so u, as an operator, represents a pure rotation:
1
u=ux − iuy
1= ux − iuy ;
the angle −θ of ux − iuy is obviously the negative of the angle θ of ux + iuy , so division
reverses rotation.
Finally, looking ahead to quaternions, Chapter 9, we have:
Multiplicative absolute value
| s | = | uv |=| u || v |, so, squaring each side, (5.80)
(s2x + s2y ) = (u2x + u2y )(v 2x + v 2y ).
5.9.8 Operator Character
We note that a number can now be considered not only as an entity in itself, but also as an
operator. In the case of complex numbers and dilative rotation, the operator character becomes
obvious.
5.9.9 Link with Matrices
The corresponding matrix representation of rotation; if u is a rotation, | u |= 1, i.e. no scaling,
and u = e ia.
s = uv (5.81)
= e iav
= (cos a + i sin a)(vx + ivy)
= (vx cos a − vy sin a) + i(vx sin a + vy cos a)
= sx + i sy ,
where we have used Euler’s formula: e iθ = cos θ + i sin θ.
Eqn. 5.81 is readily recognisable in the matrix form of eqn 5.43:
[sxsy
]=
[cos a − sin a
sin a cos a
] [vxvy
]. (5.82)
5–28
Chapter 6
Affine Transformations and HomogeneousCoordinates
6.1 Introduction
This chapter has two major objectives:
• a brief and informal introduction of points to the vector and transformation story;
• introduction of homogeneous coordinates and showing how, if we use homogeneous coordi-
nates, affine transformations behave like linear transformations, i.e. we can compose them
and combine many transformations into one.
You should note that homogeneous coordinates are not only about a convenience in the handling
of points and affine transformations. In fact, homogeneous coordinates allow us also to han-
dle projective transformations in a ‘linear’ manner — now that’s a big deal, because projective
transformations are really ugly beasts.
We will leave the following topics to Chapter 8.
• mention of the role of affine transformations in parallel projection;
• introduction of perspective projection and the corresponding perspective transformation;
• showing how perspective transformation ‘becomes linear’ if we use homogeneous coordinates.
Thus, we will arrive at a situation when most of the transformations that take place in the graphics
pipeline are made ‘linear’ (in homogeneous coordinate space) and so can all be composed into one
single matrix. This has great savings, not just because graphics hardware is fine tuned to do fast
matrix operations, but because we can compose eight or ten matrices into one (once only) and so
end doing just one matrix multiplication — on a great many points. Hence the significance of going
to homogeneous coordinates and ‘linear’ transformations which, as we have seen in section 5.2,
form a group and so, with associativity of composition, multiples of them can be combined into
one.
6–1
6.2 Points and Vectors and Affine Spaces
In section 5.4 we briefly introduced the idea of a point, P , as a vector OP from the origin, 0, to
P . That was fine, but what we kept quiet about is that vector spaces (section 3.6) have no notion
of points. We’re going to tidy up things here, but briefly.
Informally, we know what points are: for example, the point marked (P say) by the centre of the
piece of chewing gum beside where you parked your car this morning, the point marked by the
Letterkenny/Port Road corner of the main building outside the canteen (Q say), etc. We know
about vectors (as displacements), for example walk north-west 100 metres — here we’re assuming
agreement about two orthogonal basis vectors North and West.
Staying informal, we can then combine points and vectors, for example, start at point P (see
above) and move 10 metres west (the latter in italics a vector (u, say) and you will find the gold
(at point G, say).
In mathematical language: G = P + u — add a vector to a point and you get another point.
Similarly, what is the instruction (displacement) to get from P above (the corner) to Q (your car)?
We subtract P from Q; the vector v that you need to travel is given by v = P −Q. Sort of like if
you have p Euro already, and needed q Euro to buy a car, how many Euro do you need to borrow?
— q − p Euro.
An affine space comprises:
(a) A vector space, see 3.6; typical vectors are u, v, . . . ;
(b) A set of points; typical points are P,Q, . . . .
What mathematical operations are possible on the vectors and points?
(i) All the operations involving the vector space (vectors and scalars), see section 3.6;
(ii) Add a vector to a point to get a point: Q+ v = P ; if we add the zero vector to a point, we
get the same point: Q+ 0 = Q (move zero from Q and you remain at Q);
(iii) Subtract points to get a vector: v = P −Q;
As regards the vector space, we’re going to assume that a set of basis vectors, for example,
e1, e2, e3 (or i, j, k) for a three dimensional vector space, have been agreed upon.
We are nearly there. Notice that all our talk above about points was relative — how to get from
Q to P or from P to G. I don’t know if we have an Ordnance Survey (OS) map users in the class,
but the forgoing discussion was rather like (in a set of instructions to climb Errigal mountain): ‘on
the R789, find a road sign marked Dunlewey (point P , say), walk North-West 523 metres and look
for a derelict building (point Q, say); climb 239 metres North-East . . . ’.
Not good! What we need is a frame of reference like the OS uses (they call it a grid, we want
to be able to attach numbers (coordinates) to arbitrary points. Thus, ‘stop on the R251 at the
bridge at grid point 969 219 (East = 969 North = 219) . . . ’.
6–2
6.3 Frames
To get from the affine space of the previous section with its points and vectors (including the basis
vectors e1, e2, e3, all we need to do is agree on some chosen point as the origin, O. Now we can
reference all our points with respect to O and the basis vectors. Thus, (East = 969, North = 219)
means the point, P , made by adding to the origin O the vector, p (vector in lower case), made up
of 969 units of the East unit vector and 219 units of the North unit vector, P = 0 + p.
Note, incidentally, that a full grid reference has numbers before the 969 and 219, and also letters
giving zones; as far as I know, the origin of the Irish national grid is somewhere in the sea south of
Cork.
Since we have made the basis vectors are orthogonal, we have a Cartesian frame.
6.4 Affine Transformations
Already in sections 5.6.8 and 5.6.9 we have encountered affine transformations and have noted
that they are not linear and so cannot be implemented using (plain) matrices.
An affine transformation has the form of eqn. 6.1,
[vxvy
]=
[a11 a12a21 a22
] [uxuy
]+
[txty
], (6.1)
That is it contains a matrix multiplication and a vector addition — the latter a translation.
It is called affine because objects (e.g. a triangle or rectangle or straight line, . . . ) before transfor-
mation have an affinity with those after the transformation; affinity means something like ‘similar
in shape’, but please note that similar has a specific meaning in geometry.
After affine transformation:
• straight lines remain straight lines;
• parallel lines remain parallel.
Linear Transformation If, in eqn. 6.1, we remove the translation vector,
[vxvy
]=
[a11 a12a21 a22
] [uxuy
], (6.2)
we have a linear transformation. In a linear transformation vx and vy contain only linear combina-
tions of ux and uy , no (quadratic) terms in u2x or u2y for example, and no constant terms like the
translation terms tx and ty .
Incidentally, similar, see above means something different — angles and relative distances are
preserved, for example, a rectangle becomes a rectangle, whereas in linear or affine transformations,
a rectangle can change into a parallelogram.
6–3
6.5 Homogeneous Coordinates
For the reasons already stated in the introduction, section 6.1, we want to be able to express all
transformations (or as many as possible) as a matrix multiplication, see eqn. 6.2. There is a neat
trick that allows us to convert eqn. 6.1 to something that looks like eqn. 6.2.
Use of homogeneous coordinates allow us to express translation, as well as rotation and scaling,
as a matrix multiplication.
All we have to do is extend [vxvy ]t with a third coordinate vw ,
[vxvy
]→
vxvyvw
. (6.3)
In most cases, vw = 1, and if it isn’t we correct the vector so that it is; this is done by dividing
each component by vw — vw/vw = 1, unless vw = 0, in which case, see section 6.5.1.
In homogeneous coordinates, eqn. 6.1 becomes,
vxvyvw
=
a11 a12 txa21 a22 ty0 0 1
uxuyuw
. (6.4)
Let us multiply out eqn. 6.4, after setting uw = 1,
vx = a11ux + a12uy + tx
vy = a21ux + a22uy + ty
vw = 1. (6.5)
In other words, after we discard vw , the result is the same as for eqn. 6.1. Magic!
Figure 6.1 shows one way of thinking about of the homogeneous coordinates trick, which, isn’t a
trick at all, but has a proper mathematical basis; see (Schneider & Eberly 2003) for one approach,
(Dunn & Parberry 2002) and (Foley et al. 1994) and (Foley et al. 1990) for a treatment similar
to that given here; (Maxwell 1946) gives another more mathematical treatment.
The xy plane is now on the horizontal with the w axis pointing up vertically out of it. The plane at
w = 1 is the xy plane raised up one unit; think of the parallelogram in Fig. 6.1 as a sheet of glass
parallel to the x-y plane, but raised one unit of w above it (the x-y plane). Instead of representing
a point (vx , vy) as OP on the xy plane, we represent it as a line (vx , vy , vw) in three dimensional
xyw space, see OP ′ in Figure 6.1.
If, after a calculation on points, we have (vx , vy , vw) and vw 6= 1, we simply divide each component
by vw , to get (v ′x = vx/vw , v′y = vy/vw , 1); (v ′x , v
′y) now corresponds to the point P on the vw = 1
plane; now throw away vw(= 1) and (v ′x , v′y) is your desired result.
6–4
Figure 6.1: xyw Homogeneous Coordinates.
6.5.1 Point at Infinity
What if vw = 0? Dividing by (vw = 0) is a no-no. Well, the simple answer is that you should never
get vw = 0.
However, (vx , vy , vw = 0) is a neat way of representing the point at infinity. Later when you get
on to 3D projections, this will come in handy.
6.5.2 Vectors
Another interpretation of (vx , vy , vw = 0) is a vector, rather than a point. For example, let us
have point p = (px , py , pw = 1) and q = (qx , qy , qw = 1) and we subtract to get a vector,
p− q = px − qx , py − qy , 1− 1 = 0); i.e. the w component of the vector is 0.
6.5.3 Points and Vectors in OpenGL
Let us jump to 3D for a moment. If you want to specify a point (vertex) in OpenGL, one way
is to write glVertex2d(x, y);. Because OpenGL is 3D at heart and needs three coordinates,
x, y , z , for a vertex, it (OpenGL, automatically) makes z = 0 to that we have (x, y , z = 0). Next,
it creates a 4D homogeneous vector by augmenting (x, y , z = 0) with w = 1, i.e. we end up with
(x, y , z, w = 1).
In OpenGL all points (vectors) are represented by four homogeneous coordinates (x, y , z, w) and
all transformation matrices are 4× 4.
This may worry some programmers who may be concerned about performance implications —
why do 4 × 4 matrix multiplications when we could often get away with 3 × 3 or even 2 × 2?
Think of all those extra multiplications and additions. Relax. Don’t worry. For a start, if you
have a decent graphics card, most of the transformations will be done on very fast hardware with
parallelism; second, if it needs to be done in software, it doesn’t take a very clever compiler to
be able to optimise out calculations that always give the same result, or are irrelevant. Even for
programmers, the benefits of homogeneous coordinates greatly justify the costs.
6–5
6.6 2D Affine Transformations in Homogeneous Coordinates
6.6.1 Translation
See eqn. 6.4.
vxvyvw
=
1 0 tx0 1 ty0 0 1
uxuyuw
. (6.6)
i.e. we have the 2× 2 identity matrix in the top left 2× 2 submatrix, (tx , ty) as the top right-hand
2× 1 column, and finally, the bottom row of (0, 0, 1).
Just to reinforce the point, let us, as in section 6.5, multiply out eqn. 6.6, with uw = 1,
vx = ux + tx
vy = uy + ty
vw = 1. (6.7)
In other words, after we discard vw , the result is the same as for eqn. 5.50.
6.6.2 Rotation vxvyvw
=
cos b − sin b 0
sin b cos b 0
0 0 1
uxuyuw
. (6.8)
I hope it is obvious that with uw = 1 and after discarding the vw(= 1) this is the same as eqn. 5.43.
6.6.3 Scaling, Shearing, Reflection
Given what we have seen in sections 6.6.1 and 6.6.2 it should be obvious that we can create
the appropriate homogeneous representation by replacement of the top lefthand 2 × 2 matrix in
eqn. 6.8 by the matrices from eqns. 5.32 (scaling), 5.45 (shear), 5.46 (reflection about y-axis),
and 5.47 (reflection about x-axis).
6.7 Composition of Homogeneous Transformations
In section 5.2 we made a big song and dance about the fact that we could compose any number of
linear transformations, Tcomp = T1 ◦T2 ◦ · · · ◦Tn and similarly any number of matrix multiplications
that implement linear transformations and end up with the same overall transformation as if we
had applied the n transformations, successively, one at a time. We noted the savings that accrued
from this ability to compose once (n matrix multiplications) to get Tcomp, and then apply Tcompto M vectors (n multiplications followed by M matrix-vector multiplications), rather than applying
6–6
each of T1, T2, · · · , Tn to each of M points (n×M multiplications). That is, n+M as opposed to
n ×M — potentially a big saving.
Alright, does this work for homogeneous transformations. Obviously it does! But let us demon-
strate. We will rotate by angle θ and the translate by (tx , ty). The rotation matrix is
R =
cos θ − sin θ 0
sin θ cos θ 0
0 0 1
. (6.9)
The translation matrix is
T =
1 0 tx0 1 ty0 0 1
. (6.10)
Multiplying, we get
TR =
1 0 tx0 1 ty0 0 1
· cos θ − sin θ 0
sin θ cos θ 0
0 0 1
=
cos θ − sin θ txsin θ cos θ ty
0 0 1
, (6.11)
which is what we expected.
6–7
Chapter 7
3D Transformations
7.1 Introduction
In Chapter 5 we derived and discussed a number of linear transformations; then we introduced the
translation affine transformation; we noted that affine transformations cause us problems because
graphics hardware and software can take advantage of the composability of linear transformations,
i.e. we can create a composite transformation (matrix) that combines a whole raft of transforma-
tions. That means the hardware (or software) need perform just one application of a transformation
(a matrix multiplication) instead of the afore mentioned whole raft of them. When we have a great
many vertexes (points) this has a hugely beneficial effect on performance — we can display more
frames per second, or, for a chosen fixed frame rate rate, we can handle more complex and hence
more realistic virtual reality scenes.
Here we cover 3D transformations. Given what we already know about 2D transformations, this
will be relatively easy. We will be extending the 2D stuff we covered in section 6.6, so make sure
you are comfortable with what is in that section.
7.2 3D Homogeneous Coordinates
As with 2D, use of 3D homogeneous coordinates allow us to express translation, as well as rotation
and scaling, as a matrix multiplication. We start off with three coordinates [vxvyvz ]t and all we
have to do is extend [vxvyvz ]t with a fourth coordinate vw ,
vxvyvz
→vxvyvzvw
. (7.1)
As with 2D, in most cases, vw = 1, and if it isn’t we correct the vector so that it is; this is done
by dividing each component by vw — vw/vw = 1, unless vw = 0, in which case it is the point at
infinity, see section 6.5.1, or a vector, see section 6.5.2.
We have already pointed out how OpenGL represents vertexes (points), see section 6.5.3. If you
want to specify a 3D point (vertex) in OpenGL, one way is to write glVertex3d(x, y, z);.
7–1
OpenGL creates a 4D homogeneous vector by augmenting (x, y , z) with w = 1, i.e. we end up
with (x, y , z, w = 1).
As already stated, in OpenGL all points (vertexes) and vectors are represented by four homogeneous
coordinates (x, y , z, w) and all transformation matrices are 4× 4.
7.3 3D Affine Transformations in Homogeneous Coordinates
7.3.1 Translation vxvyvzvw
=
1 0 0 tx0 1 0 ty0 0 1 tz0 0 0 1
uxuyuzuw
. (7.2)
i.e. we have the 3×3 identity matrix in the top left 3×3 submatrix, (tx , ty , tz) as the top right-hand
3× 1 column, and finally, the bottom row of (0, 0, 0, 1).
7.3.2 Rotation
Rotation about the z-axis Rotation about the z-axis is in fact what we have been doing in
2D rotations. Consequently, the matrix is easy to write down; it should operate on the x- and
y-cordinates as before, and leave the z-coordinate untouched (multiplied by 1); if you rotate about
an axis, all coordinates along that axis are unaltered — make sure this is demonstrated in class.
The translation elements are zero.
Rz(b) =
cos b − sin b 0 0
sin b cos b 0 0
0 0 1 0
0 0 0 1
. (7.3)
At this stage it may be worthwhile to refer back to section 5.5 and its way of looking at transfor-
mation matrices, i.e. column 1 tells us what e1 = (1, 0, 0)t is transformed to, column 2 tells us
what e2 = (0, 1, 0)t is transformed to, and column 3 tells us what e3 = (0, 0, 1)t is transformed
to. In the cases of e1 and e2, the answer is the same as given before, for example in eqn. 5.43. In
the case of e3 = (0, 0, 1)t we see that it is transformed to itself.
Rotation about the x-axis Rotation about the x-axis should work on the y- and z-coordinates
and leave the x-coordinate alone — column 1, the x column, should be (1, 0, 0, 0)t . As always
with pure rotation matrices (about the origin), the translation elements are zero.
Rx(b) =
1 0 0 0
0 cos b − sin b 0
0 sin b cos b 0
0 0 0 1
. (7.4)
7–2
Rotation about the y-axis
Ry(b) =
cos b 0 sin b 0
0 1 0 0
− sin b 0 cos b 0
0 0 0 1
. (7.5)
7.3.3 Scaling, Shearing, Reflection
Given what we have seen in sections 7.3.1 and 7.3.2 it should be obvious that we can create the
appropriate homogeneous transformations by appropriate replacement of the top lefthand 3 × 3
matrix in eqn. 7.4.
7.4 Transformations Between Frames
In Chapter 5 we discussed how to move points around (i.e. transform their equivalent vectors).
Very often we need to do something very similar, we need to move frames around; we introduced
frames in section 6.3, i.e. a affine space which have points and vectors and notably a vector basis
and a chosen point called the origin.
Say we have a point p in world coordinates (defined in the coordinates of the world frame, for
instance a the centre point of an object in a game. Now say we have a camera with its own
camera frame and we want to know the coordinates of p in this frame.
The problem is rather similar to the transformation of points and has a rather similar solution, but
it helps to build a more sophisticated notation than we used in Chapter 5.
The situation is shown in Figure 7.1.
We have a world frame, e1w, e2w, e3w, Ow , and a camera frame e1w, e2w, e3w, Oc ; the camera frame
is rotated (counterclockwise, so the angle is positive) by an angle a and translated by the vector
d = (d1 d2)T = Oc −Ow .
But we’ll start solving the problem by considering the case where there is just rotation between
the frames, see Figure 7.2.
The notation used is one introduced by (Craig 1986) and used in books on computer vision such
as (Shapiro & Stockman 2001) and (Forsyth & Ponce 2003); as the latter books say, the notation
looks complicated at first, but once used become simple and rewards with the lack of confusion of
some apparently easier notations.
7–3
e2c
e1c
a
g
Oc Ow
e2w
e1w
e3w = e3c
e3c
p
e2e1
OcOw = Oc − Ow = (d1 d2 0)T
OwOc = Ow − Oc = (e1 e2 0)T
(vectors, so position
is irrelevant)
d1
d2
d2
d1
Figure 7.1: Transformation from world to camera coordinates.
e2w
e2c
e1w
e1c
a
e3z = e3c
Oc = Ow
d
g
f
e
Figure 7.2: Transformation from world to camera coordinates, rotation only.
7–4
Considering now only rotation as in Figure 7.2, we state without proof — but see Chapter 5 where
we discussed the anatomy of point transformation matrices.
Because we consider only rotation, we can avoid homogeneous coordinates to start with, but we
include them later when we include translation.
The transformation from world to camera is contained in the matrix cwR,
cwR =
e1w · e1c e2w · e1c e3w · e1ce1w · e2c e2w · e2c e3w · e2ce1w · e3c e2w · e3c e3w · e3c
. (7.6)
You will note that scalar (dot) product eiw · ejc gives the projection of the world basis vector eiw onto
the camera basis vector ejc, see section sec:scalar-prod-projection which discusses the projection
role of scalar product. Because both eiw and ejc are unit vectors, then the scalar product is just
the cosine of the angle between them. Note also that eiw · ejc = ejc · eiw; an easy way of justifying
this is that cos−a = cos a for any angle.
We may shed further light on eqn. 7.6 by noting that the first column of cwR is comprised of the
coordinates of e1w in the camara (c) basis; likewise the second column of cwR is comprised of the
coordinates of e2w in the camara (c) basis, etc.
The third row of cwR is comprised of the coordinates of e2c in the world (w) basis.
We write the transformation of the world coordinates (wp) of a point p to camera coordinates (cp)
as
cp = cwR wp. (7.7)
Note carefully that the subscript in cwR must be match the superscript in wp and that the superscript
in cwR must match the superscript in cp.
Discussion Examine Figure 7.2; the camera from is related to the world frame by a rotation
about the z-axis; this is the simeple planar rotation that we considered in Chapterch:vtrans.
Let us repeat eqn. 7.6 as
cwR =
e1w · e1c e2w · e1c e3w · e1ce1w · e2c e2w · e2c e3w · e2ce1w · e3c e2w · e3c e3w · e3c
, (7.8)
and use trigonometry to fill in the components,
cwR =
cos a sin a 0
− sin a cos a 0
0 0 1
. (7.9)
How does this perform when we apply eqn. 7.7,
7–5
cp = cwR wp?
We have
cp1cp2cp3
=
cos a sin a 0
− sin a cos a 0
0 0 1
wp1wp2wp3
, (7.10)
and expanding we get
cp1 = cos a wp1 − sin a wp2 + 0 wp3, (7.11)
cp3 = − sin a wp1 + cos awp2 + 0 wp3, (7.12)
cp3 = 0 wp10wp2 + 1 wp3. (7.13)
If we consider the basis vector e1w = (1 0 0)T , we see that eqn. 7.11, gives
cp1 = cos a × 1− sin a × 0 + 0× 0 = cos a × 1 = d, (7.14)
see in Figure 7.2 that this is correct, and the rest check out similarly.
Compare with point transformation If we compare the matrix in eqn. 7.10 with the point
rotation transformation in section 5.6.3, eqn. 5.43, we find
[vxvy
]=
[cos b − sin b
sin b cos b
] [uxuy
]. (7.15)
All this means is that rotating a point by an angle b is the same as rotating the frame of reference
by an equal negative angle — pretty obvious.
7.4.1 Rotation about other axes
As a consequence of what was said in the previous paragraph, we can say that the rotation part (top
left 3 × 3 matrix) of a homogeneous matrix transform (from one frame of reference to another)
is of the same form as those in eqns. 7.3, 7.4 and 7.5 with appropriate negation of the angle.
Remember sin−b = − sin b, but cos−b = cos b, so you change only the sin b terms.
7–6
7.4.2 Include Translation between Frames
When there is translation between the frames we need to resort to homogeneous coordinates as
in eqns. 7.3, 7.4 and 7.5; so, to cwR in eqn. 7.8, we need to add a fourth row 0 0 0 1 and a fourth
column, the first three elements of which represent the position of the world (w) origin, Ow , in
camera coordinates.
Hence, see Figure 7.1,
cwR =
e1w · e1c e2w · e1c e3w · e1c d1e1w · e2c e2w · e2c e3w · e2c d2e1w · e3c e2w · e3c e3w · e3c d3
0 0 0 1
. (7.16)
Here, d = (d1 d2 d3)T , gives the coordinates of Ow in camera coordinates.
7.4.3 Transformation in the opposite direction
Transformation in the opposite direction (i.e. in the example, camera coordinates to world coordi-
nates) is simple, we just use the inverse of the matrix:
wc R = c
wR−1
.
We know that the inverse of the rotation part is given by its transpose, so we have,
wc R =
e1w · e1c e1w · e2c e1w · e3c e1e2w · e1c e2w · e2c e2w · e3c e2e3w · e1c e3w · e2c e3w · e3c e3
0 0 0 1
. (7.17)
Here, e = (e1 e2 e3)T , gives the coordinates of Oc in world coordinates.
7.4.4 Concatenation of Transformations between Frames
In many cases we may need to concatenate transformations between frames, e.g. sub-model to
model (as in a game character with sub-parts, model to world, the world to camera, etc. The
possibilities are maybe even greater in machine vision or robotics.
The good news is that we can simply compose transformations as
adR = a
bRbcR
cdR. (7.18)
And that’s really good news!
7–7
Chapter 8
Projections
8.1 Introduction
Three dimensional graphics defines a 3D virtual world; but display screens are 2D, hence the need
for projection — the 3D scene must be projected onto a 2D screen or window. There are two major
types of projection: (a) orthographic projection in which the projecting light rays travel parallel to
one another and to the viewing direction; (b) perspective projection in which the projecting light
rays meet at a point called the eye point.
Orthographic projection does not involve any perspective distortion — on the display, objects close
to the viewer look the same size as similarly sized objects far away from the viewer. Orthographic
projection does have its uses, but to put the reality in virtual reality we need perspective.
Perspective projection acts like a human eye, or a camera. Objects close to the viewer look larger
than similarly sized objects far away from the viewer. In camera work, the degree of perspective
distortion is determined by the field of view (FOV) angle; a wide angle lens (wide FOV and small
focal length) produces a lot of distortion, which is why it is not recommended to do portrait
photography using a wide angle lens. On the other hand, a telephoto lens (narrow field of view
and large focal length) produces the opposite effect — because in that case perspective distortion
is much less that we are used to with our eyes and so distances seem smaller; the latter telephoto
effect is called foreshortening.
On a normal 35mm SLR camera, a 50mm lens gives about the same perspective as our eyes.
Early paintings (1400s and 1500s) had very limited perspective or none at all; consequently the
paintings looked flat and unreal. By the 1600s painters (and mathematicians) began to work out
the perspective trick and in from the late 1600s to 1900 and lot or mathematical energy was
devoted to the topic, see (Kline 1967), (Kline 1972), (Stillwell 2002), (Eves 1990b), (Katz 1998).
I’m going to be brief in this chapter. For more on cameras, see (Dunn & Parberry
2002) and (Angel 2005), (Shreiner et al. 2006) (or the online version, see my web links
http://www.jgcampbell.com/links/opengl.html). For better diagrams see (Shreiner et al.
2006) (or the online version, see above) or any of the computer graphics books mentioned in the
main bibliography.
8–1
8.2 Eyes and Cameras
[This section could do with better diagrams and description]
Figure 8.1 (from (Gonzalez & Woods 2002)) shows a simplified cross section of a human eye.
Note the lens and the light sensitive retina — a camera also has a lens and has light sensitive
sensors or film.
Figure 8.1: Cross section of the human eye.
Figure 8.2 (from (Gonzalez & Woods 2002)) shows image formation in a a human eye. A camera
operates similarly, replacing the retina with light sensitive sensors or film. Notice the centre of
projection in the lens where the projected rays all meet. A pinhole camera has no lens, but the
pinhole is the centre of projection.
Figure 8.2: Image formation in a human eye.
8–2
A general camera-based digital image sensing arrangement is shown in Figure 8.3 ((Gonzalez &
Woods 2002)). The scene element, some distance from the camera lens, is projected onto the
image plane. At the image plane there is a mosaic of light sensitive sensors; these have the effect
of transforming the two- dimensional continuous image lightness function, fi(y , x), into a discrete
function, f ′[r, c ], where r(ow) and c(olumn) are the discrete spatial coordinates; eventually, f ′[.]
gets digitised to yield a digital image, f [r, c ].
Figure 8.3: Image acquisition system (camera).
8.3 Perspective Transformation
8.3.1 Introduction
We implement perspective projection using a perspective transformation. Here we describe the
perspective transformation used by OpenGL; DirectX uses a slightly different version, we mention
that. This section follows (Lengyel 2004); (vanVerth & Bishop 2004) has a comparable coverage
with more diagrams.
In its raw form, perspective transformation involves division; it is decidedly neither linear, see
section 5 nor affine, see section 6, and looks rather ugly, and so might not be expected to be
implementable by our beloved matrices. However, owing again to the marvel of homogeneous
coordinates, we are able to make it implementable by a matrix.
The essence of perspective projection is shown in Figure 8.4(a); a line PQ in the 3D world is
projected onto P’Q’ on the projection plane. C is the centre of projection. In orthographic
(parallel) projection, Figure 8.4(b) the rays PP’ and QQ’ are parallel to one another and to the
line of sight; being parallel they do not meet at a point.
Note: in cameras and in eyes, the projection plane is behind the centre of projection. In our virtual
camera in OpenGL or DirectX we can place the camera in front of the centre of projection; there
is no difference in the effect — except that now image objects do not appear upside down.
8–3
Figure 8.4: Projections: (a) perspective; (b) orthographic (parallel).
8.3.2 Geometry of Perspective
Figure 8.5 shows a horizontal cross-section of geometry of perspective projection; the vertical
cross-section would look similar and would show the x-axis in place of the y-axis. The z-axis is
pointing in the negative direction — in OpenGL the centre of projection is always at the origin and
pointing along the negative z-axis.
The projective plane is at a distance e in front of the centre of projection. A point P = (px , py , pz)
is projected onto (x, y ,−e).
Figure 8.5: Perspective projection, cross-section.
By similar triangles we have
y = −epy/pz . (8.1)
If we imagine a similar diagram for x , we have
x = −epx/pz . (8.2)
Also,
z = −e, (8.3)
8–4
that is all z values get reduced to −e, rather like the simple orthographic projections in section 5.6.7
where projection onto the x-axis simply reduces the y value to 0 and projection onto the y-axis
reduces the x value to 0. Later, we’ll see that OpenGL does not squash z coordinates to one value,
and that it transforms them into a set of values that are useful for determining hidden surfaces
during rendering.
8.3.3 OpenGL glFrustum
The chief method of defining a perspective projection in OpenGL is to define a frustum of a pyramid
using glFrustum, see Figure 8.6; note it’s frustum, not frustrum.
Figure 8.6: Perspective view frustum volume defined by glFrustum.
glFrustum takes six arguments:
void glFrustum(GLdouble left, GLdouble right,
GLdouble bottom, GLdouble top,
GLdouble near, GLdouble far).
Respectively, these the left and right (x-axis), and the bottom and top (y-axis) extents of the field
of view; −near is the z position of the projection plane. In addition, rendering is limited to the
frustum defined by lef t, r ight, bottom, top, near, f ar ; anything outside is not rendered and these
form a 3D clipping region. In Figure 8.6 these are abbreviated l , r, b, t, n, f .
OpenGL maps the frustum given by l , r, b, t, n, f to the homogeneous clip space cube shown in
Figure 8.7. It maps: l 7→ −1, r 7→ +1, b 7→ −1, t 7→ +1
Also, to use in hidden surface removal, it retains mapped z coordinates; it maps z coordinates
thus: n 7→ −1, f 7→ +1; notice that this reverses the direction of the z axis.
8–5
Figure 8.7: Homogeneous clip space cube.
8.3.4 Derivation of Perspective Transformation
In Figure 8.6 the e of Figure 8.5 becomes n; consequently the simple raw projection eqns. 8.1 and
8.2 become
y = −npy/pz . (8.4)
For x , we likewise have
x = −npx/pz . (8.5)
Now, as noted in the previous subsection, OpenGL maps x = l 7→ x ′ = −1, x = r 7→ x ′ = +1
and y = b 7→ y ′ = −1, y = t 7→ y ′ = +1, see Figure 8.7. We can use a previously derived handy
formula, namely eqn. 5.61 repeated here as eqn. 8.6,
v = (u − u0)v1 − v0u1 − u0
+ v0, (8.6)
which maps u0 7→ v0 and u1 7→ v1, i.e. in the present case x = l 7→ x ′ = −1 and x = r 7→ x ′ = +1
and likewise for y , y ′.
For the x mapping, eqn. 8.6 gives
x ′ = (x − l)1− (−1)
r − l − 1, (8.7)
= (x − l)2
r − l − 1,
=2(x − l)− r + l
r − l ,
x ′ =2x − 2l − r + l
r − l .
So,
x ′ =2x − (r + l)
r − l . (8.8)
8–6
Substituting eqn. 8.5 into eqn. 8.8 gives,
x ′ =2n(−px
pz)− (r + l)
r − l , (8.9)
and there is a similar equation for y ′,
y ′ =2n(
−pypz
)− (t + b)
t − b . (8.10)
Thinking ahead to the required form, eqns. 8.9 and 8.10 can be rewritten
− x ′pz =2npx + (r + l)pz
r − l , (8.11)
and,
− y ′pz =2npy + (t + b)pz
t − b . (8.12)
Now pz has to be mapped; the problem here is that the rasterisation stage needs the reciprocal of
pz ( 1pz
) rather than pz so that we need z ′ = a( 1pz
) + b. Eqn. 8.6 is still usable if we define s = 1pz
and define the mappings s = −1n7→ z ′ = −1 and s = −1
f7→ z ′ = +1, so eqn. 8.6 gives
z ′ =( 1pz
+ 1n
)(1 + 1)
(−1f
+ 1n
)− 1, (8.13)
=2 f npz
+ 2f
−n + f− 1,
=−2f n
(−1pz
)+ 2f
f − n ,
−z ′pz =−2f n − 2f pz + pz f − pzn
f − n + pz ,
and finally,
− z ′pz =−pz(f + n)− 2f n
f − n . (8.14)
The coefficients of our projective transformation are in eqns. 8.11, 8.12 and 8.14, where we have
avoided the division by pz .
From the projected 3D point p′ = (x ′, y ′, z ′) (the mapped image of (px , py , pz)) we now create the
‘homogeneous’ coordinates p′ = (−x ′pz ,−y ′pz ,−z ′pz ,−pz); note the −pz in the w component.
Then summarise eqns. 8.11, 8.12 and 8.14 as
− x ′pz =2n
r − l px +r + l
r − l pz , (8.15)
− y ′pz =2n
t − bpy +t + b
t − bpz , (8.16)
and
− z ′pz = −f + n
f − npz −2nf
f − n . (8.17)
8–7
The whole projective transformation can now be written in terms of a matrix multiplication and
homogeneous coordinates,−x ′pz−y ′pz−z ′pzw ′
=
2nr−l 0 r+l
r−l 0
0 2nt−b
t+bt−b 0
0 0 − f+nf−n −
2nff−n
0 0 −1 0
pxpypz1
. (8.18)
Recall glFrustum, where we now use the same symbols as in eqn. 8.18:
void glFrustum(GLdouble l, GLdouble r,
GLdouble b, GLdouble t, GLdouble n, GLdouble f).
Figure 8.8 (Figure 3-2 of (Shreiner et al. 2006)) shows the final transformation stages in full
context.
Figure 8.8: Vertex transformation pipeline.
8–8
8.3.5 Orthographic Projection
In an orthographic or parallel projection, the projection rays are parallel to one another and to
the camera viewing direction. In addition lack of perspective distortion means that (in OpenGL)
true z coordinates can be interpolated linearly (rather than reciprocal (1/z) values). This from
(Lengyel 2004).
Consequently all that needs to be done for x and y is a mapping as given by eqn. 8.8 for x ,
x ′ =2x − (r + l)
r − l , (8.19)
=2x
r − l −r + l
r − l , (8.20)
and the same for y ,
y ′ =2x
t − b −t + b
t − b . (8.21)
The z coordinate mapping is −f → −1,−n → +1, so,
z ′ =−2z
f − n −f + n
f − n . (8.22)
We note that eqns. 8.20, 8.21 and 8.22 represent an affine transformation, i.e. scale plus
translate. Their matrix representation is given by eqn. 8.23; this is the projection matrix generated
by OpenGL function glOrtho:
void glOrtho(GLdouble left, GLdouble right,
GLdouble bottom, GLdouble top,
GLdouble near, GLdouble far).
x ′
y ′
z ′
w ′
=
2r−l 0 0 − r+l
r−l0 2
t−b 0 − t+bt−b
0 0 − 2f−n −
f+nf−n
0 0 0 1
pxpypz1
. (8.23)
8–9
Chapter 9
Quaternions
[This chapter is a bit long winded; this is because it is taken from an article written as an account
of the origins of quaternions, (Campbell 2005).]
9.1 Introduction
Quaternions are are an example hypercomplex numbers, i.e. they are an extension of complex
numbers, see section 5.9. Just as complex numbers give us a neat formula for rotating 2D vectors
and points, see eqn. 5.82, quaternions provide a neat way to rotate 3D vectors and points.
Hamilton’s treatment of complex numbers as pairs of reals, see section 5.9, led him automatically
to triples.
Figure 9.1 shows that triples are fine for describing position, e.g. P = (x1, y1, z1), and fine for
translation, Q = (x1 + tx , y1 + ty , z1 + tz). And this works in any dimensional space, three-, four-,
. . . four-dimensional space.
But what about our dilative rotation that was provided by complex multiplication?
In itself, 3D dilative rotation is fine, see (Needham 1997) p. 43 for a discussion and references;
we have closure. Problems start to arise when we consider commutativity ; dilations (scalings) and
translations do commute, but (in 3D) rotations do not commute; in operator language U ◦ V 6=V ◦ U.
For example, Ry(45o) ◦Rx(90o) 6= Rx(90o) ◦Ry(45o). On the other hand, as we will see, absence
of commutation could be tolerated.
However, applying glorious hindsight, there is another obstacle. In spite of the fact that specifica-
tion of a point in 3D requires just three numbers, a triple, a dilative rotation in 3D requires four
numbers: one for the scaling, one for the angle of rotation, and two to specify the axis of rotation
(the latter not obvious, but think latitude and longitude). On the other hand maybe the scaling
part could somehow be discarded to lose one of the degrees of freedom.
Another difficulty, the impossibility of the multiplicative absolute value property for triples, see
section 9.2.8, definitely meant that Hamilton’s work during 1830–1843 along the lines of triples
was doomed to failure.
9–1
Figure 9.1: Triples for 3D. Position, P = (x1, y1, z1); can be used for translation, Q = (x1+tx , y1+
ty , z1 + tz).
Eventually on 16th October 1843 as he was walking along the Royal Canal, it dawned on Hamilton
that he needed not triples but quadruples — quaternions.
Moving quickly to terminology and symbolism that emerged later, a quaternion q is defined as:
q = [w, v] = [w + x i + y j + zk]. (9.1)
Here v is a vector and w a scalar, both these terms were introduced by Hamilton.
9.2 The Algebra of Quaternions
Here we mostly follow (Vince 2001) which gives a nice concise treatment; in spite of being written
80 years ago, (Klein 2004/1925a) is equally easy to follow.
9.2.1 Addition
Like complex numbers and vectors we add q1 = [w1, v1] = [w1+x1i+y1j+z1k] and q2 = [w2, v2] =
[w2 + x2i + y2j + z2k],
as
q1 + q2 = [w1 + w2 + (x1 + x2)i + (y1 + y2)j + (z1 + z2)k]. (9.2)
9–2
9.2.2 Multiplication
Just like we need multiplication tables for 1, i in complex numbers (1 ·1 = 1, 1 · i = i , i ·1 = i , i · i =
−1), Hamilton needed multiplication tables for 1, i, j, k:
i2 = j2 = k2 = ijk = −1, (9.3)
jk = −kj = i,
ki = −ik = j,
ij = −ji = k.
It was anti-commutativity, e.g. jk = −kj that Hamilton accepted with most reluctance. As he
passed, Hamilton scrawled eqns. 9.4 on Broombridge; today, a plaque on the bridge commemorates
the event, see Fig. 9.2.
Figure 9.2: Plaque on Broombridge (Royal Canal, Dublin).
q1q2 = [w1 + x1i + y1j + z1k][w2 + x2i + y2j + z2k] (9.4)
= [(w1w2 − (x1x2 + y1y2 + z1z2)
+ (w1x2 + w2x1 + y1z2 − y2z1)i
+ (w1y2 + w2y1 + z1x2 − z2x1)j
+ (w1z2 + w2z1 + x1y2 − x2y1)k,
which can be written in terms of vector scalar and cross products as
p = q1q2 (9.5)
= [wp, vp]
= [(w1w2 − v1 · v2, w1v2 + w2v1 + v1 × v2].
If we set the w1, w2 (scalar parts) to zero leaving us with x1i + y1j + z1k and x2i + y2j + z2k, we
have
9–3
p = q1q2 (9.6)
= [−v1 · v2 + v1 × v2],
that is the negative of the scalar product of v1, v2, see section 3.4, and the vector product of
v1, v2, see section 3.5.
It was the latter two parts that interested Gibbs (and later Heaviside) and led him during 1880–
1900 to extract vector analysis from quaternion algebra — much to the consternation of the
quaternionists.
The scalar product of two vectors v1 and v2 is given by
v1 · v2 = x1x2 + y1y2 + z1z2, (9.7)
and the cross product by
v1 × v2 = (y1z2 − y2z1)i + (z1x2 − z2x1)j + (x1y2 − x2y1)k. (9.8)
9.2.3 Inverse
q = [w, v] = [w + x i + y j + zk]. (9.9)
q−1 =[w − (x i + y j + zk)]
(| q |)2 =q∗
(| q |)2 , (9.10)
where | q |=√w 2 + x2 + y 2 + z2, and we term q∗ = [w − (x i + y j + zk)] the conjugate of q. The
similarity with inverse complex number and complex conjugate, eqn. 5.79 is clear.
This gives us unique inverse of multiplication — division.
9.2.4 Scalar Products — no inverse
We can contrast vector scalar product u ·v which as no unique inverse. In Fig. 9.3 we see u ·v = a
for a whole family of v, v′, . . . . Hence a/u is satisfied by any of an infinity of v, v′ . . . that project
onto u an amount a.
9.2.5 Point as Quaternion
If we represent the point P = (x, y , z) as a position vector, we can represent it as the quaternion
p = [0 + x i + y j + zk]. (9.11)
9–4
Figure 9.3: No unique inverse of u · v = a.
9.2.6 Rotation Quaternion
The quaternion which represents a rotation of φ about the axis n = (n1 n2 n3)T is given by
q = [cosφ
2+ sin
φ
2(n1i + n2j + n3k)] = [cos
φ
2+ sin
φ
2n]. (9.12)
q is a unit quaternion so | q |= 1; also n is a unit vector.
See (Campbell 2005) for a discussion of the origins of eqn. 9.12.
Eqn. 9.13 gives the equation for quaternion rotation, rotated point is p′,
p′ = qpq−1. (9.13)
9.2.7 Rotation — an example
From (Vince 2001).
We want to rotate the point P = (0, 1, 1) by 90o about the y-axis, see Fig. 9.4.
1. The point P is p = [0 + 0i + 1j + 1k];
2. The axis of rotation (y -axis) is given by n = 0i + 1j + 0k) = (0 1 0)T ;
3. The rotation quaternion is q = [cos 45o + sin 45on1i + sin 45on2j + sin 45on3k]
= [cos 45o + 0i + sin 45o j + 0k]
= [cos 45o + sin 45o j];
9–5
Figure 9.4: Rotation of the point P = (0, 1, 1) by 90o about the y-axis (0i + 1j + 0k); the result
is P ′ = (1, 1, 0).
4. Likewise, the inverse rotation quaternion is q−1 = [cos 45o − sin 45o j]; since a rotation
quaternion is unit, the denominator of eqn. 9.10 is 1;
5. Compute p′ = qpq−1:
(a)
qp = [cos 45o + sin 45o j]
×[0 + 0i + 1j + 1k]
= [cos 45o j + cos 45ok
+ sin 45o j2 + sin 45o jk
= [− sin 45o + sin 45o i + cos 45o j + cos 45ok],
recalling the multiplication rules for j2 = −1 and jk = i in eqn. 9.4;
(b)
(qp)q−1 = [− sin 45o + sin 45o i + cos 45o j + cos 45ok]
×[cos 45o − sin 45o j]
= − sin 45o cos 45o + sin 45o sin 45o j
+ sin 45o cos 45o i− sin 45o sin 45o ij
+ cos 45o cos 45o j− cos 45o sin 45o j2
+ cos 45o cos 45ok− cos 45o sin 45okj
= (− sin 45o cos 45o + cos 45o sin 45o)
+(sin 45o cos 45o + cos 45o sin 45o)i
+(sin 45o sin 45o + cos 45o cos 45o)j
+(cos 45o cos 45o − sin 45o sin 45o)k
= [0, (1, 1, 0)],
9–6
again noting multiplication rules for j2 = −1, ij = k, and kj = −i and sin 45o =
cos 45o = 1√2
.
6. So P ′ = (1, 1, 0). Fig. 9.4 shows that this is the correct answer.
9.2.8 Multiplicative Absolute Value
(Stillwell 2002) p. 389, (Klein 2004/1925a), p. 66.
Recall eqn. 5.80 on the multiplicative absolute value of complex numbers. Quite correctly, Hamilton
was guided by this goal for what turned out to be his quaternions — but what for many years he
hoped to be triples.
If we take eqn. 9.5:
p = q1q2 (9.14)
= [wp, vp]
= [(w1w2 − v1 · v2, w1v2 + w2v1 + v1 × v2],
the equation corresponding to eqn. 5.80 for quaternions is
(w 21 + x21 + y 21 + z21 )(w 22 + x22 + y 22 + z22 ) = w 2p + x2p + y 2p + z2p (9.15)
= (w1w2 − x1x2 − y1y2 − z1z2)2
+(w1x2 + x1w2 + y1z2 − z1y2)2
+(w1y2 − x1z2 + y1w2 + z1x2)2
+(w1z2 + x1y2 − y1x2 + z1w2)2.
Euler (in 1748) had proved this (that the product of sums of four squares is a sum of four squares)
and he and Legendre has used it. Legendre had proved that the same for triples is in general
impossible (in general the product of sums of three squares is not a sum of three squares).
It was only in 1844 that Hamilton’s friend Graves realised the impact of this proof. So, the search
for a solution based on triples was certainly futile. It seems that the property is valid only for
dimensionalities 1, 2, 4, and 8.
Incidentally, Hamilton introduced the term tensor for the sums of squares contained in eqn. 9.15.
9.3 Why use Quaternions?
See (Dunn & Parberry 2002).
9–7
• Four real numbers specify a rotation; nine required for a rotation matrix; on the other hand
Euler angle three. Maybe not a big issue these days, but consider a five minute animation of
an object with 20 rotating parts and the animation needing updating every 25th of a second:
5 × 60 × 20 × 25 × n = 150, 000 × n real numbers where n = 4 for quaternions and n = 9
for rotation matrices;
• Smooth interpolation — probably the greatest reason for using quaternions;
• Fast concatenation of rotations; rotation matrix concatenation is easy too, but not Euler
angles;
• Fast inversion of rotations; rotation matrix inversion is easy too, but not Euler angles;
• No gimbal lock? Opinion is divided — maybe gimbal lock reappears when we apply the
rotation to real actuators?
9.4 Conversions to and from Quaternions etc.
• Euler angles to and from rotation matrices;
• Quaternions to and from rotation matrices;
• Quaternions to and from Euler angles.
See (Dunn & Parberry 2002) and Appendix E.
9.5 Software
Appendix E contains software implementations in Java.
9–8
Appendix A
Abstract Algebra on a Set S
Thanks to Ken Adams (Adams 2005) for this. See also (Eves 1965/1990a) and (MacLane &
Birkoff 1999). The properties are cumulative.
Semi-group
Closed under + a, b ∈ S, a + b ∈ S,
Associative + (a + b) + c = a + (b + c).
Monoid
Identity ∃0 ∈ S, a + 0 = 0 + a = a.
Group
Inverse ∀a ∈ S, ∃a′, a + a′ = a′ + a = 0.
Abelian Group
Commutative + a + b = b + a.
Ring
Closed under multiplication (·) a, b ∈ S, a · b ∈ S,
Multiplication is associative (a · b) · c = a · (b · c),
Multiplication is distributive over addition a · (b + c) = a · b + a · c ,
Addition is distributive over multiplication (a + b) · c = a · c + b · c .
Integral Domain
Multiplication is commutative a · b = b · a,
Multiplicative identity ∃1 ∈ S, 1 · a = a · 1 = a,
No divisors of 0 a · b = 0⇒ a = 0 or b = 0; hence, a · b = a · c ⇒ b = c .
Field
Multiplication inverse ∀a 6= 0 ∃a−1 · a = a · a−1 = 1; hence division ab
= a · b−1.
Note: a field is a ring where S/{0} is a multiplicative group under ·.
A–1
Appendix B
Vectors and 2D Transformations in Java
B.1 Vector2DH.java
/**
* Vector2DH.java
* 3D homogeneous vector
* @author j.g.c.
* version 1.0; 2008-01-08, from Vector2D.java
* based on Vector4D.java
*/
import java.io.*; import java.awt.geom.*;
import javax.swing.*;
import java.awt.*;
public class Vector2DH –
double[] d; // package visible
public static final double EPS = 1.0E-20; // very small
public static final int N = 3;
/**
* void constructor
*/
public Vector2DH()–
d= new double[N];
˝
/**
* Constructor from double array
* @param d array
*/
public Vector2DH(double[] dd)–
this();
int n = dd.length;
d[2] = 1.0; // unless overridden by dd
if(n¿ N)n = N;
B–1
for(int i= 0; i¡ n; i++)d[i]= dd[i];
˝
/**
* Constructor from two values
* @params components
*/
public Vector2DH(double x, double y)–
this();
d[0]= x; d[1]= y; d[2] = 1.0;
˝
/**
* Constructor from two values
* @params components
*/
public Vector2DH(double x, double y, double w)–
this();
d[0]= x; d[1]= y; d[2] = w;
˝
/**
* from length and angle
* cannot make constructor, conflicts with
* public Vector2DH(double x, double y)
* @params length, angle
*/
public static Vector2DH fromLengthAngle(double len, double ang)–
double x = len*Math.cos(ang);
double y = len*Math.sin(ang);
return new Vector2DH(x, y);
˝
/**
* sets all elements to parameter
* @param value
*/
public void setAll(double val)–
for(int i= 0; i¡ N; i++)d[i]= val;
˝
/**
* sets element index i to parameter
* @param val value
* @param index index
*/
public void set(int i, double val)–
d[i]= val;
˝
/**
B–2
* sets element 0 to parameter
* @param val value
*/
public void setX(double val)–
d[0]= val;
˝
/**
* sets element 1 to parameter
* @param val value
*/
public void setY(double val)–
d[1]= val;
˝
/**
* sets element 2 to parameter
* @param val value
*/
public void setW(double val)–
d[2]= val;
˝
/**
* gets element index i
* @param index index
*/
public double get(int i)–
return d[i];
˝
/**
* gets element index 0
* @return value of X
*/
public double getX()–
return d[0];
˝
/**
* gets element index 1
* @return value of Y
*/
public double getY()–
return d[1];
˝
/**
* returns sum of this and parameter
B–3
* @param v vector to be added to this
* @return sum
*/
public Vector2DH add(Vector2DH v)–
Vector2DH v1= new Vector2DH();
for(int i= 0; i¡ N; i++)v1.d[i]= this.d[i] + v.d[i];
return v1;
˝
/**
* adds parameter to this
* @param v vector to be added
*/
public void addTo(Vector2DH v)–
for(int i= 0; i¡ N; i++)this.d[i]= this.d[i] + v.d[i];
˝
/**
* static add
* @param v1, v2 vectors to be added
* @return sum
*/
public static Vector2DH add(Vector2DH v1, Vector2DH v2)–
System.out.println(”v1 = ”+ v1);
System.out.println(”v2 = ”+ v2);
Vector2DH v= new Vector2DH(v1.d[0], v1.d[1], v1.d[2]);
v.addTo(v2);
System.out.println(”v = ”+ v);
return v;
˝
/**
* static scalar/dot product; note: w ignored
* @param v1, v2 vectors to be scalar multiplied
* @return sum
*/
public static double dot(Vector2DH v1, Vector2DH v2)–
return v1.d[0]*v2.d[0] + v1.d[1]*v2.d[1];
˝
/**
* static angle (between two vectors)
* @param v1, v2 vectors to be scalar multiplied
* @return sum
*/
public static double angle(Vector2DH v1, Vector2DH v2)–
double len1 = v1.length();
double len2 = v2.length();
B–4
assert len1 ¿ EPS;
assert len2 ¿ EPS;
return Math.acos(dot(v1, v2)/(len1*len2));
˝
/**
* static add of Point2D and Vector2DH
* @param p, v Point2D and Vector2DH to be added
* @return p + v
*/
public static Point2D add(Point2D p, Vector2DH v)–
return new Point2D.Double(p.getX() + v.getX(),
p.getY() + v.getY() );
˝
/**
* make a Point2D from a Vector2DH
*/
public Point2D toPoint2D()–
return new Point2D.Double(getX(), getY() );
˝
/**
* returns difference of this and parameter
* @param v vector to be subtracted this
* @return difference
*/
public Vector2DH sub(Vector2DH v)–
Vector2DH v1= new Vector2DH();
for(int i= 0; i¡ N; i++)v1.d[i]= this.d[i] - v.d[i];
return v1;
˝
/**
* returns this scaled by s
* @param s scale
* @return scaled result
*/
public Vector2DH scale(double s)–
Vector2DH v1= new Vector2DH();
for(int i= 0; i¡ N; i++)v1.d[i] = s*this.d[i];
return v1;
˝
/**
* returns length of vector; note: ignores w
* @return length
*/
public double length()–
B–5
double len= 0.0;
len= d[0]*d[0] + d[1]*d[1];
return Math.sqrt(len);
˝
/**
* returns angle of vector
* @return angle
*/
public double angle()–
double ang = Math.atan2(d[1], d[0]);
if(ang ¡ 0.0) ang = ang + Math.toRadians(360.0);
return ang;
˝
/**
* creates formatted String; note: ignores w
* @return string
*/
public String toString()–
StringBuffer s= new StringBuffer(”(”);
for(int i= 0; i¡ N-1; i++)–
s.append(String.format(”%5.1f”, d[i]) );
if(i!= N-2)s.append(”, ”);
else s.append(”)”);
˝
return s.toString();
˝
/**
* creates formatted String (polar format)
* @return string
*/
public String toStringPolar()–
StringBuffer s= new StringBuffer(”[”);
s.append(String.format(”%5.1f”, length()) );
s.append(”, ”);
s.append(String.format(”%5.1f”, Math.toDegrees(angle() ) ) );
s.append(”]”);
return s.toString();
˝
public void draw(Point2D ps, Color c, Graphics2D g2,
String s, int dx, int dy)–
RenderingHints hPrev = g2.getRenderingHints();
RenderingHints hints = new RenderingHints(
B–6
RenderingHints.KEY˙ANTIALIASING,
RenderingHints.VALUE˙ANTIALIAS˙ON);
g2.setRenderingHints(hints);
Point2D pe = new Point2D.Double(ps.getX() + getX(), ps.getY() + getY());
Line2D line = new Line2D.Double(ps, pe);
Color cPrev = g2.getColor();
g2.setColor(c); // exactly the same as setPaint
g2.draw(line);
// arrowhead
Vector2DH va1 = Vector2DH.fromLengthAngle(
6.0, angle() + Math.toRadians(160.) );
Point2D pe1 = Vector2DH.add(pe, va1);
Vector2DH va2 = Vector2DH.fromLengthAngle(
6.0, angle() + Math.toRadians(200.) );
Point2D pe2 = Vector2DH.add(pe, va2);
Line2D l1 = new Line2D.Double(pe, pe1);
g2.draw(l1);
Line2D l2 = new Line2D.Double(pe, pe2);
g2.draw(l2);
// draw text, first must invert axes
AffineTransform at = new AffineTransform();
at.scale(1, -1); /* and flip y axis back again for text
also note the ’-’ on the y below */
g2.transform(at);
// text beyond arrowpoint of vector
double ang = angle();
double dang = Math.abs(Math.toDegrees(angle()) );
int offset = 0;
if(dang ¿ 150.0 && dang ¡ 210.0) offset = 15;
Vector2DH va3 = Vector2DH.fromLengthAngle(5.0, ang);
Point2D pe3 = Vector2DH.add(pe, va3);
g2.drawString(s, (int)pe3.getX() - offset + dx,
-((int)pe3.getY() + dy));
// and transform back again
g2.transform(at);
g2.setColor(cPrev);
g2.setRenderingHints(hPrev);
˝
public void draw(Point2D ps, Color c, Graphics2D g2, String s)–
draw(ps, c, g2, s, 0, 0);
˝
public void draw(Point2D ps, Color c, Graphics2D g2)–
draw(ps, c, g2, ””, 0, 0);
˝
B–7
˝
B.2 Test Program for Vector2DH.java
/**
* Vector2DHT1.java
* tests Vector2DH
* @author j.g.c.
* version 1.0; 2008-01-07, from Vector2DT1
*/
import java.io.*;
public class Vector2DHT1 –
/**
* Constructor
*/
public Vector2DHT1()–
double[] d= –1.0, 2.0˝;
Vector2DH x1= new Vector2DH(d);
System.out.println(”Vector2DHT1”);
System.out.println(”x1 = ”+ x1.toString());
Vector2DH x2= new Vector2DH();
x2.setAll(10.0);
System.out.println(”x2 = ”+ x2.toString());
Vector2DH x3 = x2.add(x1);
System.out.println(”x3 = x2.add(x1): ”+ x3.toString());
Vector2DH x4 = x3.scale(0.25);
System.out.println(”x4 = x3.scale(0.25): ”+ x4.toString());
double len = x1.length();
System.out.println(”len = x1.length(): ”+ len);
˝
/** The main method constructs the test
*/
public static void main(String[] args)
– new Vector2DHT1(); ˝
˝
B–8
B.3 Transform2D.java
/**
* Transform2D.java
* Affine transforms for 2D vector
* @author j.g.c.
* version 2.0; 2008-01-06; now uses homogeneous form
* version 1.0; 2005-10-08
* based partly on Transform4D.java
*/
import java.io.*;
public class Transform2D –
private double[][] m; // transform matrix
public static final int N = 3;
private static final double eps = 1.0e-20;
/**
* void constructor
*/
public Transform2D()–
m = new double[N][N];
setIdentity();
˝
/**
* copy constructor
*/
public Transform2D(Transform2D other)–
m = new double[N][N];
for(int r = 0; r¡ N; r++)–
for(int c = 0; c¡ N; c++)–
m[r][c] = other.m[r][c];
˝
˝
˝
/**
* Constructor from double arrays
* @param mm matrix array
* @param tt translation array
*/
public Transform2D(double[][] mm)–
this();
for(int r = 0; r¡ N; r++)–
B–9
for(int c = 0; c¡ N; c++)–
m[r][c]= mm[r][c];
˝
˝
˝
/**
* sets all elements to parameter
* @param val
*/
public void setAll(double val)–
for(int r = 0; r¡ N; r++)–
for(int c = 0; c¡ N; c++)–
m[r][c] = val;
˝
˝
˝
/**
* sets identity matrix and zero translation
*/
public void setIdentity()–
setAll(0.0);
for(int i = 0; i¡ N; i++)–
m[i][i] = 1.0;
˝
˝
/**
* sets translation transform
* @param t translation as a vector
*/
public void setTrans(Vector2DH tt)–
setIdentity();
m[0][2] = tt.getX();
m[1][2] = tt.getY();
˝
/**
* sets translation
* @params tx x tx component
* @params ty y tx component
*/
public void setTrans(double tx, double ty)–
setIdentity();
m[0][2] = tx;
m[1][2] = ty;
˝
B–10
/**
* sets rotation
* @param theta angle (radians)
*/
public void setRot(double theta)–
setIdentity();
double cos = Math.cos(theta);
double sin = Math.sin(theta);
m[0][0] = cos;
m[0][1] = -sin;
m[1][0] = sin;
m[1][1] = cos;
// zero translation
m[0][2] = 0.0;
m[1][2] = 0.0;
˝
/**
* sets scaling
* @param s scale components as a vector
*/
public void setScale(Vector2DH s)–
setIdentity();
for(int i= 0; i¡ N; i++)m[i][i]= s.get(i);
˝
/**
* sets scaling
* @params sx x scale component
* @params sy y scale component
*/
public void setScale(double sx, double sy)–
setIdentity();
m[0][0]= sx;
m[1][1]= sy;
˝
/**
* returns pre-multiplication of this*parameter
* @param a a vector to be multiplied by this
* @return product v = this*u
*/
public Vector2DH apply(Vector2DH u)–
Vector2DH v = new Vector2DH();
double dot;
for (int r = 0; r ¡ N; r++) –
dot = 0.0;
for (int c = 0; c ¡ N; c++) –
B–11
dot += m[r][c]*u.d[c];
˝
v.set(r, dot);
˝
return v;
˝
public Vector2DH transform(Vector2DH u)–
return apply(u);
˝
/**
* composition
* returns pre-multiplication of parameter*this
* right translations rotated and added to left translations
* *** 2005-10-08 check
* @param a transform to be composed with this
* @return product b = a*this
*/
public Transform2D compose(Transform2D a)–
Transform2D b = new Transform2D();
double dot;
for (int r = 0; r ¡ N; r++) –
for (int c = 0; c ¡ N; c++) –
dot = 0.0;
for (int i = 0; i ¡ N; i++) –
dot += a.m[r][i]*this.m[i][c];
˝
b.m[r][c] = dot;
˝
˝
return b;
˝
/**
* static multiplication
* @param a transform
* @param b transform
* @return product a*b
*/
public static Transform2D mult(Transform2D a, Transform2D b)–
Transform2D e = new Transform2D();
double dot;
for (int r = 0; r ¡ N; r++) –
for (int c = 0; c ¡ N; c++) –
dot = 0.0;
for (int i = 0; i ¡ N; i++) –
dot += a.m[r][i]*b.m[i][c];
˝
e.m[r][c] = dot;
B–12
˝
˝
return e;
˝
/**
* creates formatted String
* @return string
*/
public String toString()–
StringBuffer s= new StringBuffer(”[”);
for(int r= 0; r¡ N-1; r++)–
s.append(”(”);
for(int c= 0; c¡ N; c++)–
s.append(String.format(”%8.3f”, m[r][c]) );
if(c!= N-1)s.append(”, ”);
else s.append(”)”);
˝
if(r!= N-2)s.append(”“n”);
˝
s.append(”]“n”);
return s.toString();
˝
˝
B.4 Test for Transform2D.java
/**
* Transform2DT1.java
* tests Transform2D and Vector2DH
* @author j.g.c.
* version 1.1; 2005-10-08, 2008-01-06
* version 2.0; 2008-01-06, homogeneous vectors Vector2DH
* from Transform4DT1.java
*/
import java.io.*;
public class Transform2DT1 –
/**
* Constructor
*/
public Transform2DT1()–
System.out.println(”Transform2DT1”);
B–13
double[] d= –1.0, 2.0˝;
Vector2DH va= new Vector2DH(d);
System.out.println(”va = ”+ va.toString());
Transform2D f = new Transform2D();
f.setIdentity();
System.out.println(”f.setIdentity(): “n”+ f.toString());
Transform2D g = new Transform2D();
g.setIdentity();
System.out.println(”g.setIdentity(): “n”+ g.toString());
Transform2D fg= Transform2D.mult(f,g);
System.out.println(”fg= Transform2D.mult(f,g) (g identity): “n”+ fg.toString());
Vector2DH vb = f.transform(va);
System.out.println(”vb = f.apply(va): ”+ vb.toString());
double[] d1= –0.0, 1.0˝;
Vector2DH v0= new Vector2DH(d1);
System.out.println(”v0 = ”+ v0.toString());
double[] dt= –2.0, 3.0˝;
Vector2DH t= new Vector2DH(dt);
System.out.println(”t = ”+ t.toString());
Transform2D h = new Transform2D();
h.setTrans(t);
System.out.println(”h.setTrans(t): “n”+ h.toString());
Vector2DH v1 = h.transform(v0);
System.out.println(”v1 = h.transform(v0): “n”+ v1.toString());
//90 degree rotation of (1,0) should make (0,1)
Transform2D R90 = new Transform2D();
R90.setRot(Math.toRadians(90.0));
System.out.println(”R90.setRot(Math.toRadians(90.0)): “n”+ R90.toString());
Vector2DH v10 = new Vector2DH(1.0, 0.0);
System.out.println(”v10 = ”+ v10.toString());
v1= R90.transform(v10);
System.out.println(”v1 = R90.transform(v10): “n”+ v1.toString());
System.out.println(”“n“n---- Rotation followed by translation ...”);
Transform2D R30 = new Transform2D();
R30.setRot(Math.toRadians(30.0));
System.out.println(”R30.setRot(Math.toRadians(30.0)): “n”+ R30.toString());
Vector2DH v21 = new Vector2DH(2.0, 1.0);
System.out.println(”v21 = ”+ v21.toString());
B–14
v1= R30.transform(v21);
System.out.println(”v1 = R30.transform(v21): “n”+ v1.toString());
Transform2D T12 = new Transform2D();
T12.setTrans(new Vector2DH(1.0, 2.0));
System.out.println(”T12.setTrans(new Vector2DH(1.0, 2.0)): “n”
+ T12.toString());
Vector2DH v2 = T12.transform(v1);
System.out.println(” v2 = T12.transform(v1): ”+ v2.toString());
System.out.println(”“n“n-- Multiplying translation * rotation ...”);
// for incorrect order see Transform4DT1
Transform2D TR = new Transform2D();
TR = Transform2D.mult(T12, R30); // this is T12*R30
System.out.println(”RT = Transform2D.mult(T12, R30): “n”+ TR.toString());
System.out.println(”v21 = ”+ v21.toString());
Vector2DH v3 = TR.transform(v21);
System.out.println(” v3 = g.transform(v21): ”+ v3.toString());
System.out.println(”“n“n---- Translation followed by rotation...”);
System.out.println(”T12 : “n”
+ T12.toString());
System.out.println(”v21 = ”+ v21.toString());
Vector2DH v4 = T12.transform(v21);
System.out.println(”v4 = T12.transform(v1): ”+ v4.toString());
R30 = new Transform2D();
R30.setRot(Math.toRadians(30.0));
//Vector2DH v21 = new Vector2DH(2.0, 1.0);
System.out.println(”v4 = ”+ v4.toString());
Vector2DH v5= R30.transform(v4);
System.out.println(” v5 = R30.transform(v4): ”+ v5.toString());
System.out.println(”“n“n-- Multiplying rotation * translation ...”);
// for incorrect order see Transform4DT1
Transform2D RT = new Transform2D();
RT = Transform2D.mult(R30, T12); // this is T12*R30
System.out.println(”RT = Transform2D.mult(R30, T12): “n”+ RT.toString());
System.out.println(”v21 = ”+ v21.toString());
Vector2DH v6 = RT.transform(v21);
System.out.println(” v6 = g.transform(v21): ”+ v6.toString());
˝
/** The main method constructs the test
*/
public static void main(String[] args)
– new Transform2DT1(); ˝
B–15
˝
B–16
Appendix C
3D Affine Transformations in Java
C.1 Homogeneous 3D Vector — Vector4D.java
package com.jgcampbell.graphicsmaths;
/**
* Vector4D.java
* 4D homogeneous components vector
* @author j.g.c.
* version 1.0; 2005-03-20
* based partly on Brackeen, Developing Games in Java, p. 340 ...
* and Lengyel, Mathematics for 3D Game Programming and Computer
* Graphics, 2nd ed.
*/
import java.io.*;
public class Vector4D –
double[] d; // package visible
public static final int N = 4;
/**
* void constructor
*/
public Vector4D()–
d= new double[N];
˝
/**
* Constructor from double array
* @param d array
*/
public Vector4D(double[] dd)–
this();
for(int i= 0; i¡ N; i++)d[i]= dd[i];
C–1
˝
/**
* Constructor from double array
* @params components
*/
public Vector4D(double x, double y, double z, double w)–
this();
d[0]= x; d[1]= y; d[2]= z; d[3]= w;
˝
/**
* sets all elements to parameter
* @param value
*/
public void setAll(double val)–
for(int i= 0; i¡ N; i++)d[i]= val;
˝
/**
* returns sum of this and parameter
* @param v vector to be added to this
* @return sum
*/
public Vector4D add(Vector4D v)–
Vector4D v1= new Vector4D();
for(int i= 0; i¡ N; i++)v1.d[i]= this.d[i] + v.d[i];
return v1;
˝
/**
* returns difference of this and parameter
* @param v vector to be subtracted this
* @return difference
*/
public Vector4D sub(Vector4D v)–
Vector4D v1= new Vector4D();
for(int i= 0; i¡ N; i++)v1.d[i]= this.d[i] - v.d[i];
return v1;
˝
/**
* returns this scaled by s
* @param s scale
* @return scaled result
*/
public Vector4D scale(double s)–
Vector4D v1= new Vector4D();
for(int i= 0; i¡ N; i++)v1.d[i] = s*this.d[i];
C–2
return v1;
˝
/**
* returns length of vector
* @return length
*/
public double length()–
double len= 0.0;
for(int i= 0; i¡ N; i++)len+= d[i]*d[i];
return Math.sqrt(len);
˝
/**
* creates formatted String
* @return string
*/
public String toString()–
StringBuffer s= new StringBuffer(”(”);
for(int i= 0; i¡ N; i++)–
s.append(String.format(”%8.3f”, d[i]) );
if(i!= N-1)s.append(”, ”);
else s.append(”)”);
˝
return s.toString();
˝
˝
C.2 Test for Vector4D.java
package com.jgcampbell.graphicsmaths;
/**
* Vector4DT1.java
* tests Vector4D
* @author j.g.c.
* version 1.0; 2005-10-29
*/
import java.io.*;
public class Vector4DT1 –
/**
* Constructor
*/
C–3
public Vector4DT1()–
double[] d= –1.0, 2.0, 3.0, 4.0˝;
Vector4D x1= new Vector4D(d);
System.out.println(”Vector4DT1”);
System.out.println(”x1 = ”+ x1.toString());
Vector4D x2= new Vector4D();
x2.setAll(10.0);
System.out.println(”x2 = ”+ x2.toString());
Vector4D x3 = x2.add(x1);
System.out.println(”x3 = x2.add(x1): ”+ x3.toString());
Vector4D x4 = x3.scale(0.25);
System.out.println(”x4 = x3.scale(0.25): ”+ x4.toString());
double len = x1.length();
System.out.println(”len = x1.length(): ”+ len);
˝
/** The main method constructs the test
*/
public static void main(String[] args)
– new Vector4DT1(); ˝
˝
C.3 Homogeneous 3D Vector Transformations — Trans-
form4D.java
package com.jgcampbell.graphicsmaths;
/**
* Transform4D.java
* transform for 4D homogeneous vector
* @author j.g.c.
* version 1.0; 2005-03-20
* version 1.1; 2005-07-13, adding axis-angle rotation
* based partly on Brackeen, Developing Games in Java, p. 340 ...
* and Lengyel, Mathematics for 3D Game Programming and Computer
* Graphics, 2nd ed.
* see also Foley-VanDam, Introduction to Computer Graphics, p180 ...
*/
import java.io.*;
C–4
public class Transform4D –
double[][] m;
public static final int N = 4;
private static final double eps = 1.0e-20;
/**
* void constructor
*/
public Transform4D()–
m = new double[N][N];
˝
/**
* Constructor from double array
* @param mm array
*/
public Transform4D(double[][] mm)–
this();
for(int r = 0; r¡ N; r++)–
for(int c = 0; c¡ N; c++)–
m[r][c]= mm[r][c];
˝
˝
˝
/**
* sets all elements to parameter
* @param value
*/
public void setAll(double val)–
for(int r = 0; r¡ N; r++)–
for(int c = 0; c¡ N; c++)–
m[r][c] = val ;
˝
˝
˝
/**
* sets identity matrix
*/
public void setIdentity()–
setAll(0.0);
for(int i = 0; i¡ N; i++)m[i][i] = 1.0;
˝
/**
* sets translation transform
* @param t translation as a vector
*/
C–5
public void setTrans(Vector4D t)–
setIdentity();
int n= N-1;
for(int c = 0; c¡ n; c++)m[c][n] = t.d[c]; // x, y, z
˝
/**
* sets rotation-about-z transform
* @param theta angle (radians)
*/
public void setRotZ(double theta)–
setIdentity();
double cos = Math.cos(theta);
double sin = Math.sin(theta);
m[0][0] = cos;
m[0][1] = -sin;
m[1][0] = sin;
m[1][1] = cos;
˝
/**
* sets rotation-about-x transform
* @param theta angle (radians)
*/
public void setRotX(double theta)–
setIdentity();
double cos = Math.cos(theta);
double sin = Math.sin(theta);
m[1][1] = cos;
m[1][2] = -sin;
m[2][1] = sin;
m[2][2] = cos;
˝
/**
* sets rotation-about-y transform
* @param theta angle (radians)
*/
public void setRotY(double theta)–
setIdentity();
double cos = Math.cos(theta);
double sin = Math.sin(theta);
m[0][0] = cos;
m[0][2] = sin;
m[2][0] = -sin;
m[2][2] = cos;
˝
/**
C–6
* sets rotation-about-arbitrary axis transform
* see Hill, Computer graphics using OpenGL, 2nd ed. p. 241.
* @param theta angle (radians)
* @param u axis
*/
public void setRotAxis(double theta, Vector4D u)–
setIdentity();
double c = Math.cos(theta);
double s = Math.sin(theta);
double cc = 1.0 - c;
double ux = u.d[0], uy = u.d[1], uz = u.d[2];
m[0][0] = c + cc*ux*ux;
m[0][1] = cc*uy*ux - s*uz;
m[0][2] = cc*uz*ux + s*uy;
m[1][0] = cc*ux*uy + s*uz;
m[1][1] = c + cc*uy*uy;
m[1][2] = cc*uz*uy - s*ux;
m[2][0] = cc*ux*uz - s*uy;
m[2][1] = cc*uy*uz + s*ux;
m[2][2] = c + cc*uz*uz;
˝
/**
* gets angle and axis from
* rotation-about-arbitrary axis transform
* see Hill, Computer graphics using OpenGL, 2nd ed. p. 241.
* @param theta angle (radians)
* @param u axis
*/
public double getRotAxis(Vector4D u)–
double theta = Math.acos(0.5*(m[0][0] + m[1][1] + m[2][2] - 1.0));
double s2 = 2.0*Math.sin(theta);
if(Math.abs(theta) ¿ eps)–
u.d[0] = (m[2][1] - m[1][2])/s2;
u.d[1] = (m[0][2] - m[2][0])/s2;
u.d[2] = (m[1][0] - m[0][1])/s2;
u.d[3] = 0.0;
˝
return theta;
˝
/**
* sets scaling
* @param s scale components as a vector
*/
C–7
public void setScale(Vector4D s)–
setIdentity();
int n= N-1;
for(int i= 0; i¡ n; i++)m[i][i]= s.d[i];
˝
/**
* returns pre-multiplication of this*parameter
* @param v a vector to be multiplied by this
* @return product b = this*v
*/
public Vector4D mpy(Vector4D a)–
Vector4D b = new Vector4D();
double dot;
for (int r = 0; r ¡ N; r++) –
dot = 0;
for (int c = 0; c ¡ N; c++) –
dot += m[r][c]*a.d[c];
˝
b.d[r] = dot;
˝
return b;
˝
/**
* returns pre-multiplication of parameter*this
* @param a transform to be multiplied by this
* @return product b = a*this
*/
public Transform4D mpy(Transform4D a)–
Transform4D b = new Transform4D();
double dot;
for (int r = 0; r ¡ N; r++) –
for (int c = 0; c ¡ N; c++) –
dot = 0.0;
for (int i = 0; i ¡ N; i++) –
dot += a.m[r][i]*this.m[i][c];
˝
b.m[r][c] = dot;
˝
˝
return b;
˝
/**
* creates formatted String
* @return string
*/
C–8
public String toString()–
StringBuffer s= new StringBuffer(”[”);
for(int r= 0; r¡ N; r++)–
s.append(”(”);
for(int c= 0; c¡ N; c++)–
s.append(String.format(”%8.3f”, m[r][c]) );
if(c!= N-1)s.append(”, ”);
else s.append(”)”);
˝
if(r!= N-1)s.append(”“n”);
else s.append(”]”);
˝
return s.toString();
˝
˝
C.4 Test for Transform4D.java
package com.jgcampbell.graphicsmaths;
/**
* Transform4DT1.java
* tests Transform4D
* @author j.g.c.
* version 1.0; 2005-03-20
* version 1.1; 2005-07-13
*/
import java.io.*;
public class Transform4DT1 –
/**
* Constructor
*/
public Transform4DT1()–
System.out.println(”Transform4DT1”);
double[] d= –1.0, 2.0, 3.0, 4.0˝;
Vector4D xa= new Vector4D(d);
System.out.println(”xa = ”+ xa.toString());
Transform4D f = new Transform4D();
f.setIdentity();
System.out.println(”f.setIdentity(): “n”+ f.toString());
C–9
Transform4D g = new Transform4D();
g.setIdentity();
System.out.println(”g.setIdentity(): “n”+ g.toString());
Transform4D fg= f.mpy(g);
System.out.println(”fg = f.mpy(g) (g identity): “n”+ fg.toString());
Vector4D xb = f.mpy(xa);
System.out.println(”xb = f.mpy(xa): ”+ xb.toString());
double[] d1= –0.0, 0.0, 0.0, 1.0˝;
Vector4D x0= new Vector4D(d1);
System.out.println(”x0 = ”+ x0.toString());
double[] dt= –1.0, 2.0, 3.0, 0.0˝;
Vector4D t= new Vector4D(dt);
System.out.println(”t = ”+ t.toString());
Transform4D h = new Transform4D();
h.setTrans(t);
System.out.println(”h.setTrans(t): “n”+ h.toString());
Vector4D x1 = h.mpy(x0);
System.out.println(”x1 = h.mpy(x0): “n”+ x1.toString());
//pi/2 rotation of (1,0,0) should make (0,1,0)
h.setRotZ(Math.PI/2.0);
System.out.println(”h.setRotZ(Math.PI/2): “n”+ h.toString());
Vector4D x1001 = new Vector4D(1.0, 0.0, 0.0, 1.0);
System.out.println(”x1001 = ”+ x1001.toString());
x1= h.mpy(x1001);
System.out.println(”x1 = h.mpy(x1001): “n”+ x1.toString());
System.out.println(”“nRotation followed by translation ...”);
h.setRotZ(Math.PI/4.0);
System.out.println(”h.setRotZ(Math.PI/4.0): “n”+ h.toString());
Vector4D x2101 = new Vector4D(2.0, 1.0, 0.0, 1.0);
System.out.println(”x2101 = ”+ x2101.toString());
x1= h.mpy(x2101);
System.out.println(”x1 = h.mpy(x2101): “n”+ x1.toString());
f.setTrans(new Vector4D(1.0, 2.0, 0.0, 0.0));
System.out.println(”f.setTrans(new Vector4D(1.0, 2.0, 0.0, 0.0)): ”
+ f.toString());
Vector4D x2 = f.mpy(x1);
System.out.println(”x2 = f.mpy(x1): ”+ x2.toString());
// incorrect order
/*
C–10
System.out.println(”“nConcatenating rotation and translation ...”);
g = f.mpy(h); // this is h.f
System.out.println(”g = f.mpy(h): “n”+ g.toString());
x2 = g.mpy(x2101);
System.out.println(”x2 = g.mpy(x2101): ”+ x2.toString());
*/
System.out.println(”“nConcatenating rotation and translation ...”);
g = h.mpy(f); // this is f.h
System.out.println(”g = h.mpy(f): “n”+ g.toString());
x2 = g.mpy(x2101);
System.out.println(”x2 = g.mpy(x2101): ”+ x2.toString());
System.out.println(”“nAxis-angle rotation,”);
System.out.println(”see Hill, p. 241, ex. 5.3.4 ...”);
Transform4D ru = new Transform4D();
Vector4D u= new Vector4D(0.577, 0.577, 0.577, 0);
ru.setRotAxis(Math.PI/4.0, u);
System.out.println(”u= new Vector4D(0.577, 0.577, 0.577, 0);”);
System.out.println(”ru.setRotAxis(Math.PI/4.0, u): “n”
+ ru.toString());
double th = ru.getRotAxis(u);
System.out.println(”double th = getRotAxis(u);”);
System.out.println(”th = ”+ th);
System.out.println(”u = ”+ u.toString() );
˝
/** The main method constructs the test
*/
public static void main(String[] args)
– new Transform4DT1(); ˝
˝
C–11
Appendix D
3D Affine Transformations in C++
D.1 Homogeneous 3D Vector — Vector4D.h
//--------------------------------------------------------
// Vector4D.h
// j.g.c. 2005-03-28
//--------------------------------------------------------
#ifndef Vector4DH
#define Vector4DH
#include ¡string¿
#include ¡iostream¿
#include ¡cstdio¿ // for toString/sprintf
using namespace std; // bad bad practice in a header file, but easy!
#define N 4
class Vector4D–
static int n˙;
// friends are not members, but have access to private data
friend ostream& operator¡¡(ostream& os, const Vector4D& v);
friend istream& operator¿¿(istream& os, Vector4D& v);
friend bool operator¡(const Vector4D& lhs, const Vector4D& rhs);
public:
Vector4D();
Vector4D(float d[]);
Vector4D(float x, float y, float z, float w);
Vector4D(istream& is);
void setAll(float val);
Vector4D add(Vector4D v);
Vector4D sub(Vector4D v);
D–1
Vector4D scale(float s);
double length();
string toString() const;
//note below public
float d˙[N];
˝;
#undef N
#endif
D.2 Homogeneous 3D Vector — Vector4D.cpp
//--------------------------------------------------------
// Vector4D.cpp
// j.g.c. 2005-03-28
//--------------------------------------------------------
#include ”Vector4D.h”
int Vector4D::n˙ = 4;
Vector4D::Vector4D()–
setAll(0.0);
˝
Vector4D::Vector4D(float d[])–
for(int i= 0; i¡ n˙; i++)d˙[i]= d[i];
˝
Vector4D::Vector4D(float x, float y, float z, float w)–
d˙[0]= x; d˙[1]= y; d˙[2]= z; d˙[3]= w;
˝
Vector4D::Vector4D(istream& is)–
for(int i= 0; i¡ n˙; i++)is¿¿ d˙[i];
˝
void Vector4D::setAll(float val)–
for(int i= 0; i¡ n˙; i++)d˙[i]= val;
˝
Vector4D Vector4D::add(Vector4D v)–
Vector4D v1= Vector4D();
for(int i= 0; i¡ n˙; i++)v1.d˙[i]= d˙[i] + v.d˙[i];
return v1;
˝
D–2
Vector4D Vector4D::sub(Vector4D v)–
Vector4D v1= Vector4D();
for(int i= 0; i¡ n˙; i++)v1.d˙[i]= d˙[i] - v.d˙[i];
return v1;
˝
Vector4D Vector4D::scale(float s)–
Vector4D v1= Vector4D();
for(int i= 0; i¡ n˙; i++)v1.d˙[i] = s*d˙[i];
return v1;
˝
double Vector4D::length()–
double len= 0.0;
for(int i= 0; i¡ n˙; i++)len+= d˙[i]*d˙[i];
return sqrt(len);
˝
ostream& operator¡¡ (ostream& os, const Vector4D& v)
–
os¡¡ v.toString();
return os;
˝
string Vector4D::toString() const
–
//ideally, I should be using stringstream here, but there
// appears to be a problem under g++ 2.96 j.c. 2003/02/22
char cc[80];
sprintf(cc, ”(%8.3f, %8.3f, %8.3f, %8.3f)”, d˙[0], d˙[1], d˙[2], d˙[3]);
return string(cc);
˝
istream& operator¿¿(istream& is, Vector4D& v)
–
// assumes that fields are separated by whitespace
for(int i= 0; i¡ v.n˙; i++)is¿¿v.d˙[i];
return is;
˝
D.3 Test for Vector4D.cpp
/**
* Vector4DT1.cpp
* tests Vector4D
D–3
* author j.g.c.
* version 1.0; 2005-03-28
*/
#include ¡iostream¿ // for cout¡¡ etc.
#include ¡string¿
#include ¡fstream¿ // for files - ofstream, ifstream
#include ”Vector4D.h”
using namespace std;
int main()–
float d[4]= –1., 2., 3., 4.˝;
Vector4D x1= Vector4D(d);
cout¡¡”Vector4DT1”¡¡ endl;
cout¡¡ ”x1 = ” ¡¡ x1.toString()¡¡ endl;
Vector4D x2= Vector4D();
x2.setAll(10.0);
cout¡¡ ”x2 = ”¡¡ x2.toString()¡¡ endl;
Vector4D x3 = x2.add(x1);
cout¡¡ ”x3 = x2.add(x1): ”¡¡ x3.toString()¡¡ endl;
Vector4D x4 = x3.scale(0.25);
cout¡¡ ”x4 = x3.scale(0.25): ”¡¡ x4.toString()¡¡ endl;
float len = x1.length();
cout¡¡ ”len = x1.length(): ”¡¡ len¡¡ endl;
˝
D.4 Homogeneous 3D Vector Transformations — Trans-
form4D.h
//--------------------------------------------------------
// Transform4D.h
// j.g.c. 2005-03-28
//--------------------------------------------------------
#ifndef Transform4DH
#define Transform4DH
#include ¡string¿
#include ¡iostream¿
#include ¡cstdio¿ // for toString/sprintf
D–4
#include ”Vector4D.h”
#define N 4
using namespace std; // bad practice, but easy!
class Transform4D–
static int n˙;
// friends are not members, but have access to private data
friend ostream& operator¡¡(ostream& os, const Transform4D& v);
friend istream& operator¿¿(istream& os, Transform4D& v);
friend bool operator¡(const Transform4D& lhs, const Transform4D& rhs);
public:
Transform4D();
Transform4D(float d[N][N]);
Transform4D(istream& is);
void setAll(float val);
void setIdentity();
void setTrans(Vector4D t);
void setRotZ(double theta);
void setRotX(double theta);
void setRotY(double theta);
void setScale(Vector4D s);
Vector4D mpy(Vector4D v);
//b = a*this
Transform4D mpy(Transform4D s);
string toString() const;
private:
float m˙[N][N];
˝;
#undef N
#endif
D.5 Homogeneous 3D Vector Transformations — Trans-
form4D.cpp
//--------------------------------------------------------
// Transform4D.cpp
// j.g.c. 2005-03-28
D–5
//--------------------------------------------------------
#include ”Transform4D.h”
#define N 4
int Transform4D::n˙ = N;
Transform4D::Transform4D()–
setAll(0.0);
˝
Transform4D::Transform4D(float m[N][N])–
for(int r = 0; r¡ n˙; r++)–
for(int c = 0; c¡ n˙; c++)–
m˙[r][c]= m[r][c];
˝
˝
˝
Transform4D::Transform4D(istream& is)–
for(int r= 0; r¡ n˙; r++)–
for(int c= 0; c¡ n˙; c++)–
is¿¿ m˙[r][c]; // undoubtedly wrong; test
˝
˝
˝
void Transform4D::setAll(float val)–
for(int r = 0; r¡ n˙; r++)–
for(int c = 0; c¡ n˙; c++)–
m˙[r][c] = val ;
˝
˝
˝
void Transform4D::setIdentity()–
setAll(0.0);
for(int i = 0; i¡ n˙; i++)m˙[i][i] = 1.0;
˝
void Transform4D::setTrans(Vector4D t)–
setIdentity();
int n= n˙-1;
for(int c = 0; c¡ n; c++)m˙[c][n] = t.d˙[c]; //x, y, z
˝
void Transform4D::setRotZ(double theta)–
setIdentity();
double cs = cos(theta);
D–6
double sn = sin(theta);
m˙[0][0] = cs;
m˙[0][1] = -sn;
m˙[1][0] = sn;
m˙[1][1] = cs;
˝
void Transform4D::setRotX(double theta)–
setIdentity();
double cs = cos(theta);
double sn = sin(theta);
m˙[1][1] = cs;
m˙[1][2] = -sn;
m˙[2][1] = sn;
m˙[2][2] = cs;
˝
void Transform4D::setRotY(double theta)–
setIdentity();
double cs = cos(theta);
double sn = sin(theta);
m˙[0][0] = cs;
m˙[0][2] = -sn;
m˙[2][0] = sn;
m˙[2][2] = cs;
˝
void Transform4D::setScale(Vector4D s)–
setIdentity();
int n= n˙-1;
for(int i = 0; i¡ n; i++)m˙[i][i] = s.d˙[i]; //x, y, z
˝
/**
* returns pre-multiplication of this*parameter
* @param v a vector to be multiplied by this
* @return product b = this*v
*/
Vector4D Transform4D::mpy(Vector4D a)–
Vector4D b = Vector4D();
double dot;
for (int r = 0; r ¡ N; r++) –
dot = 0;
for (int c = 0; c ¡ N; c++) –
dot += m˙[r][c]*a.d˙[c];
˝
b.d˙[r] = dot;
˝
return b;
D–7
˝
/**
* returns pre-multiplication of parameter*this
* @param a transform to be multiplied by this
* @return product b = a*this
*/
Transform4D Transform4D::mpy(Transform4D a)–
Transform4D b = Transform4D();
double dot;
for (int r = 0; r ¡ N; r++) –
for (int c = 0; c ¡ N; c++) –
dot = 0.0;
for (int i = 0; i ¡ N; i++) –
dot += a.m˙[r][i]*m˙[i][c];
˝
b.m˙[r][c] = dot;
˝
˝
return b;
˝
ostream& operator¡¡ (ostream& os, const Transform4D& m)
–
os¡¡ m.toString();
return os;
˝
string Transform4D::toString() const–
//ideally, I should be using stringstream here, but there
// appears to be a problem under g++ 2.96 j.c. 2003/02/22
char cc[80];
string s= string(”[”);
for(int r= 0; r¡ n˙; r++)–
sprintf(cc, ”(%8.3f, %8.3f, %8.3f, %8.3f)“n”,
m˙[r][0], m˙[r][1], m˙[r][2], m˙[r][3]);
s+= string(cc);
˝
s+= ”]”;
return s;
˝
istream& operator¿¿(istream& is, Transform4D& m)–
// assumes that fields are separated by whitespace
for(int r= 0; r¡ m.n˙; r++)–
for(int c= 0; c¡ m.n˙; c++)–
is¿¿ m.m˙[r][c];
˝
D–8
˝
return is;
˝
#undef N
D.6 Test for Transform4D.cpp
/**
* Transform4DT1.cpp
* tests Transform4D
* @author j.g.c.
* version 1.0; 2005-03-28
*/
#include ¡iostream¿ // for cout¡¡ etc.
#include ¡string¿
#include ¡fstream¿ // for files - ofstream, ifstream
#include ”Transform4D.h”
#include ”Vector4D.h”
using namespace std;
class Transform4DT1 –
public:
/**
* Constructor
*/
Transform4DT1()–
cout¡¡ ”Transform4DT1”¡¡ endl;
float d[4]= –1.0, 2.0, 3.0, 4.0˝;
Vector4D xa= Vector4D(d);
cout¡¡ ”xa = ”+ xa.toString()¡¡ endl;
Transform4D f = Transform4D();
f.setIdentity();
cout¡¡ ”f.setIdentity(): “n”+ f.toString()¡¡ endl;
Transform4D g = Transform4D();
g.setIdentity();
cout¡¡ ”g.setIdentity(): “n”¡¡ g.toString()¡¡ endl;
Transform4D fg= f.mpy(g);
cout¡¡ ”fg = f.mpy(g) (g identity): “n”¡¡ fg.toString()¡¡ endl;
D–9
Vector4D xb = f.mpy(xa);
cout¡¡ ”xb = f.mpy(xa): ”¡¡ xb.toString()¡¡ endl;
float d1[4]= –0.0, 0.0, 0.0, 1.0˝;
Vector4D x0= Vector4D(d1);
cout¡¡ ”x0 = ”¡¡ x0.toString()¡¡ endl;
float dt[4]= –1.0, 2.0, 3.0, 0.0˝;
Vector4D t= Vector4D(dt);
cout¡¡ ”t = ”+ t.toString()¡¡ endl;
Transform4D h = Transform4D();
h.setTrans(t);
cout¡¡ ”h.setTrans(t): “n”¡¡ h.toString()¡¡ endl;
Vector4D x1 = h.mpy(x0);
cout¡¡ ”x1 = h.mpy(x0): “n”¡¡ x1.toString()¡¡ endl;
float pi= 3.1415926;
//pi/2 rotation of (1,0,0) should make (0,1,0)
h.setRotZ(pi/2.0);
cout¡¡ ”h.setRotZ(pi/2): “n”¡¡ h.toString()¡¡ endl;
Vector4D x1001 = Vector4D(1.0, 0.0, 0.0, 1.0);
cout¡¡ ”x1001 = ”¡¡ x1001.toString()¡¡ endl;
x1= h.mpy(x1001);
cout¡¡ ”x1 = h.mpy(x1001): “n”¡¡ x1.toString()¡¡ endl;
cout¡¡ ”“nRotation followed by translation ...”¡¡ endl;
h.setRotZ(pi/4.0);
cout¡¡ ”h.setRotZ(pi/4.0): “n”¡¡ h.toString()¡¡ endl;
Vector4D x2101 = Vector4D(2.0, 1.0, 0.0, 1.0);
cout¡¡ ”x2101 = ”¡¡ x2101.toString()¡¡ endl;
x1= h.mpy(x2101);
cout¡¡ ”x1 = h.mpy(x2101): “n”¡¡ x1.toString()¡¡ endl;
f.setTrans(Vector4D(1.0, 2.0, 0.0, 0.0));
cout¡¡ ”f.setTrans(Vector4D(1.0, 2.0, 0.0, 0.0)): ”¡¡
f.toString()¡¡ endl;
Vector4D x2 = f.mpy(x1);
cout¡¡ ”x2 = f.mpy(x1): ”¡¡ x2.toString()¡¡ endl;
// incorrect order
/*
System.out.println(”“nConcatenating rotation and translation ...”);
g = f.mpy(h); // this is h.f
System.out.println(”g = f.mpy(h): “n”+ g.toString());
x2 = g.mpy(x2101);
System.out.println(”x2 = g.mpy(x2101): ”+ x2.toString());
*/
D–10
cout¡¡ ”“nConcatenating rotation and translation ...”¡¡ endl;
g = h.mpy(f); // this is f.h
cout¡¡ ”g = h.mpy(f): “n”¡¡ g.toString()¡¡ endl;
x2 = g.mpy(x2101);
cout¡¡ ”x2 = g.mpy(x2101): ”¡¡ x2.toString()¡¡ endl;
˝
˝;
/** The main method constructs the test
*/
int main()–
Transform4DT1();
˝
D–11
Appendix E
Quaternions in Java
E.1 Quaternion class — Quaternion.java
package com.jgcampbell.graphicsmaths;
/**
* Quaternion.java
* @author j.g.c.
* version 1.0; 2005-11-24
* based on Dunn and Parberry
*/
import java.io.*;
public class Quaternion –
double w, x, y, z;
private static final double eps = 1.0e-20;
/**
* void constructor
*/
public Quaternion()–
w= 0.0; x = 0.0; y= 0.0; z= 0.0;
˝
/**
* Constructor from double array
* @param d array, w = d[0]
*/
public Quaternion(double[] d)–
w= d[0]; x= d[1]; y= d[2]; z= d[3];
˝
/**
* sets identity
*/
E–1
public void setIdentity()–
w= 1.0; x= 0.0; y= 0.0; z = 0.0;
˝
/**
* sets rotation-about-z transform
* @param theta angle (radians)
*/
public void setRotZ(double theta)–
double cos = Math.cos(theta/2);
double sin = Math.sin(theta/2);
setIdentity();
w = cos; z= sin;
˝
/**
* sets point quaternion
* @param p Vector4D holding point
*/
public void setPoint(Vector4D p)–
w = 0.0; x = p.d[0]; y = p.d[1]; z = p.d[2];
˝
/**
* sets rotation-about-x transform
* @param theta angle (radians)
*/
public void setRotX(double theta)–
setIdentity();
double cos = Math.cos(theta/2);
double sin = Math.sin(theta/2);
w = cos; x= sin;
˝
/**
* sets rotation-about-y transform
* @param theta angle (radians)
*/
public void setRotY(double theta)–
setIdentity();
double cos = Math.cos(theta/2);
double sin = Math.sin(theta/2);
w = cos; y= sin;
˝
/**
* sets rotation-about-arbitrary axis transform
* @param theta angle (radians)
* @param u axis
E–2
*/
public void setRotAxis(double theta, Vector4D u)–
double cos = Math.cos(theta/2);
double sin = Math.sin(theta/2);
w= cos;
x = u.d[0]*sin; y = u.d[1]*sin; z = u.d[2]*sin;
˝
/**
* gets angle and axis from
* rotation-about-arbitrary axis transform
* @param theta angle (radians)
* @param u axis
*/
/*public double getRotAxis(Vector4D u)–
double theta = Math.acos(0.5*(m[0][0] + m[1][1] + m[2][2] - 1.0));
double s2 = 2.0*Math.sin(theta);
if(Math.abs(theta) ¿ eps)–
u.d[0] = (m[2][1] - m[1][2])/s2;
u.d[1] = (m[0][2] - m[2][0])/s2;
u.d[2] = (m[1][0] - m[0][1])/s2;
u.d[3] = 0.0;
˝
return theta;
˝
*/
/**
* returns pre-multiplication of this*parameter
* @param a a quaternion to be multiplied by this
* @return product b = this*a
*/
public Quaternion mpy(Quaternion a)–
Quaternion b = new Quaternion();
b.w = w*a.w - x*a.x - y*a.y - z*a.z;
b.x = w*a.x + x*a.w + y*a.z - z*a.y;
b.y = w*a.y + y*a.w + z*a.x - x*a.z;
b.z = w*a.z + z*a.w + x*a.y - y*a.x;
return b;
˝
/**
* add
* @param other a quaternion to be added this
* @return product b = this + a
*/
public Quaternion add(Quaternion other)–
Quaternion b = new Quaternion();
b.w = w+ other.w; b.x = x + other.x;
E–3
b.y = y+ other.y; b.z = z + other.z;
return b;
˝
/**
* scale
* @param double s scale factor
*/
public void scale(double s)–
w*= s; x*= s; y*= s; z*= s;
˝
/**
* lenSq
* @param a a quaternion
* @return length-squared(this)
*/
public double lenSq()–
return w*w + x*x + y*y + z*z;
˝
/**
* conjg
* @param a a quaternion to be conjugated
* @return product b = inverse(a)
*/
public Quaternion conjg()–
Quaternion b = new Quaternion();
b.w = w; b.x = - x;
b.y = - y; b.z = - z;
return b;
˝
/**
* inv
* @param a a quaternion to be inverted
* @return product b = inverse(this)
*/
public Quaternion inv()–
Quaternion b = new Quaternion();
b = this.conjg();
double aa= this.lenSq();
b.scale(1/aa);
return b;
˝
/**
* creates formatted String
* @return string
E–4
*/
public String toString()–
StringBuffer s= new StringBuffer(”[”);
s.append(String.format(”%8.3f”, w) );
s.append(” (”);
s.append(String.format(”%8.3f ”, x) );
s.append(String.format(”%8.3f ”, y) );
s.append(String.format(”%8.3f ”, z) );
s.append(”)]”);
return s.toString();
˝
˝
E.2 Example Program using Quaternions
package com.jgcampbell.graphicsmaths;
/**
* QuaternionT1.java
* tests Quaternion
* @author j.g.c.
* version 1.0; 2005-11-24
*/
import java.io.*;
public class QuaternionT1 –
/**
* Constructor
*/
public QuaternionT1()–
System.out.println(”QuaternionT1”);
double[] d= –0.0, 1.0, 1.0, 1.0˝;
Vector4D p= new Vector4D(d);
System.out.println(”p = ”+ p);
Quaternion pp = new Quaternion();
pp.setPoint(p);
System.out.println(”pp.setPoint(p): “n”+ pp);
Quaternion q = new Quaternion();
q.setRotY(Math.PI/2);
E–5
System.out.println(”q.setRotY(Math.PI/2): “n”+ q);
Quaternion qi = q.inv();
System.out.println(”qi = q.inv(): “n”+ qi);
Quaternion qp= q.mpy(pp);
System.out.println(”qp= q.mpy(p): “n”+ qp);
Quaternion qpqi= qp.mpy(qi);
System.out.println(”qpqi= qp.mpy(qi): “n”+ qpqi);
q.setRotZ(-Math.PI/2);
System.out.println(”q.setRotZ(Math.PI/2): “n”+ q);
qi = q.inv();
System.out.println(”qi = q.inv(): “n”+ qi);
qp= q.mpy(pp);
System.out.println(”qp= q.mpy(p): “n”+ qp);
qpqi= qp.mpy(qi);
System.out.println(”qpqi= qp.mpy(qi): “n”+ qpqi);
˝
/** The main method constructs the test
*/
public static void main(String[] args)
– new QuaternionT1(); ˝
˝
E–6
Bibliography
Adams, K. (2005). Abstract Algebra on a Set S. Personal communication.
Angel, E. (2005). Interactive Computer Graphics: a top-down approach using OpenGL, 4th edn,
Addison Wesley.
Brannan, D. A., Esplen, M. F. & Gray, J. J. (1999). Geometry, Cambridge University Press. ISBN:
0521597870.
Buss, S. R. (2003). 3-D Computer Graphics: a mathematical introduction with OpenGL, Cam-
bridge University Press.
Campbell, J. (2005). The Origins of Quaternions and Related Algebras, Technical report, Let-
terkenny Institute of Technology. URL. http://www.jgcampbell.com/hamilton/quatorig.pdf.
Coxeter, H. M. (1969/1989). Introduction to Geometry, 2nd edn, Wiley. ISBN: 0-471-50458-0.
Craig, J. (1986). Introduction to Robotics Mechanics and Control, Addison Wesley.
Crowe, M. J. (1985/1967). A History of Vector Analysis : The Evolution of the Idea of a Vectorial
System, Dover.
Dunn, F. & Parberry, I. (2002). 3D math primer for graphics and game development, Wordware
Publishers. ISBN: 1556229119.
Eves, H. (1965/1990a). Foundations and Fundamental Concepts of Mathematics, 3rd edn, Dover.
Eves, H. (1990b). An Introduction to the History of Mathematics, 6th edn, Thompson/Brooks-
Cole.
Foley, J., van Dam, A., Feiner, S. & Hughes, J. (1990). Computer Graphics: principles and
practice, 2nd edn, Addison Wesley.
Foley, J., van Dam, A., Feiner, S., Hughes, J. & Phillips, R. (1994). Introduction to Computer
Graphics, Addison Wesley.
Forsyth, D. A. & Ponce, J. (2003). Computer Vision - a modern approach, Prentice Hall.
Gonzalez, R. & Woods, R. (2002). Digital Image Processing, 2nd edn, Prentice Hall.
Hoffmann, B. (1975/1966). About Vectors, Dover.
Katz, V. J. (1998). A History of Mathematics: An Introduction, 2nd edn, Addison Wesley.
Klein, F. (2004/1925a). Elementary Mathematics from an Advanced Standpoint: Arithmetic
Algebra Analysis, Dover.
E–1
Klein, F. (2004/1925b). Elementary Mathematics from an Advanced Standpoint: Geometry,
Dover.
Kline, M. (1967). Mathematics for the Nonmathematician, Dover.
Kline, M. (1972). Mathematical Thought from Ancient to Modern Times, Vol. 1, Oxford University
Press.
Lengyel, E. (2004). Mathematics for 3d Game Programming and Computer Graphics, 2nd edn,
Charles River Media.
Lipschutz, S. & Lipson, M. (1999). Linear Algebra (Schaum’s Outlines), 3rd edn, McGraw-Hill.
MacLane, S. & Birkoff, G. (1999). Algebra, 3rd edn, AMS Chelsea Publishing.
Maxwell, E. (1946). The Methods of Plane Projective Geometry based on the use of General
Homogeneous Coordinates, Cambridge University Press.
Needham, T. (1997). Visual Complex Analysis, Oxford.
Schneider, P. J. & Eberly, D. H. (2003). Geometric Tools for Computer Graphics, Morgan Kauf-
mann. ISBN: 1-55860-594-0.
Shapiro, L. G. & Stockman, G. C. (2001). Computer Vision, Prentice Hall.
Shreiner, D., Woo, M., Neider, J. & Davis, T. (2006). OpenGL Programming Guide: The
Official Guide to Learning OpenGL, Version 2 (The Red Book), 5th edn, Addison Wes-
ley. ISBN: 0-321-33573-2. Note that there is a very usable web version of Edition 1.1 at:
http://www.glprogramming.com/red/.
Spiegel, M. (1959). Vector Analysis (Schaum’s Outlines), McGraw-Hill.
Stillwell, J. (2002). Mathematics and its History, 2nd edn, Springer-Verlag.
Strang, G. (2003). Introduction to linear algebra, 3rd edn, Wellesley-Cambridge Press. ISBN:
0961408898.
vanVerth, J. M. & Bishop, L. M. (2004). Essential mathematics for games and interactive appli-
cations, Morgan Kaufmann. ISBN: 155860863X.
Vince, J. (2001). Essential mathematics for computer graphics fast, Springer-Verlag. ISBN:
1852333804.
E–2
Top Related