Computer Graphics via Java Ian Ferguson

Ab-libris the e-book people

British Library Cataloguing in Publication Data Ferguson, Robert Ian Computer Graphics via Java (Ab-libris computing classics series) 1. Computer Graphics I. Title II. Ferguson, Robert Ian, 1966ISBN: 1-903561-08-6 Typeset in Durham by Ab-Libris Ltd.

Copyright notice

Copyright 2002 by Ab-libris Ltd. All rights reserved. No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form or by any means, electronic, mechanical, photocopying, recording or otherwise, without the prior written permission of Ab-Libris Ltd., PO Box 287, Durham, DH1 5YZ, UK

Dedication

To Jackie and Andrew

Our little systems have their day; They have their day and cease to be; They are but broken lights of Thee, And Thou, O Lord, art more than they. From: In Memoriam A.H.H. - OBIIT MDCCCXXXIII. By Alfred, Lord Tennyson

Foreword

This book is based upon an undergraduate class on Computer Graphics (52.359) taught at the University of Strathclyde. The author would like to thank past students for their comments on the material, without which, this book would never have taken shape. At first sight, a book on CG with Java is a bit of an odd beast; Java after all is not renowned for its speed of execution and if there is one application area which demands high speeds, it is CG and games. Given that the majority of Computer Science courses are now taught using Java, it does however make sense from a pedagogical point of view to continue using a familiar language when studying an unfamiliar application area. Much material, including demonstration animated applets, further examples and answers to (some) of the exercises in the book can be found on the website dedicated to the 52.359 class. The page can be found at: http://www.cs.strath.ac.uk/~if/classes/52.359 I hope you derive as much fun from the material contained in the book as I did when I first encountered CG.

Ian Ferguson Jan. 2002

Contents Chapter 1 Chapter 2

What is “Computer Graphics”?

12

1.1

12

Concepts, Terms and Definitions

24

2.1 2.2

Introduction Low level concepts 2.2.1 Memory Mapping 2.2.2 Resolution 2.2.3 Screen size 2.2.4 Coordinates 2.2.5 The origin 2.2.6 Colour 2.2.7 Image files 2D Drawing 2.3.1 Points 2.3.2 Lines 2.3.3 Shapes

24 25 26 28 28 29 30 30 32 33 33 35 35

A first graphics program

38

3.1 3.2 3.3 3.4

38 40 43 44

2.3

Chapter 3

Chapter 4

Introduction

Introduction The features of a simple graphics program Organising your work for Java Graphics Primitives

Graphics Primitives

53

4.1 4.2

53 53 54 58 60 62 63 63 65 67 69

4.3

Introduction Drawing straight lines 4.2.1 Brute force 4.2.2 Bresenham’s algorithm 4.2.3 An implementation of the line drawing algorithms Circles 4.3.1 Brute force 4.3.2 Cheating with 8 arcs 4.3.3 Digital Differential Analysis - An incremental algorithm 4.3.4 Bresenham’s algorithm for circles 4.3.5 An implementation of the circle drawing algorithms

Chapter 5

Data Structures and Drawing

75

5.1 5.2

75 75 76 77 81 82 85 85 86 87 88 89 91 92 92 92

5.3

5.4 5.5 5.6

Chapter 6

Chapter 7

Introduction The basic 2d data structure 5.2.1 Point2d class 5.2.2 Line2d Class 5.2.3 Shape2d class 5.2.4 Drawing2d class Adding methods 5.3.1 Point2d class 5.3.2 Line2d Class 5.3.3 Shape2d class 5.3.4 Drawing2d class 5.3.5 Adding these classes to the simple drawing application The completed system The Dry Run Further methods 5.6.1 Drawing vs erasing

2d Transformations

95

6.1 6.2

95 95 95 96 98 101 103 104 105 108

Introduction Transformations the simple way 6.2.1 What is a transformation? 6.2.2 Translation 6.2.3 Rotation - (about the origin) 6.2.4 Scaling 6.2.5 Identity transformations 6.2.6 Order of transformations 6.2.7 Rotation around local origin 6.2.8 Other transformations - general cases/ special cases.

Transformations as matrices

111

7.1 7.2

111 111 111 112 112 112 113 113 113 113 116 118 118

7.3 7.4

Introduction The Transformations 7.2.1 Rotation 7.2.2 Scaling 7.2.3 Translation 7.2.4 Homogenous coordinates 7.2.5 Homogenous rotation 7.2.6 Homogenous scaling 7.2.7 Homogenous translation Implementing Matrices Using the matrix class 7.4.1 Simple transformation using a matrix 7.4.2 Combination of transformations using matrices

Chapter 8

Simple Animation and Interaction

122

8.1 8.2

122 123 123 124 125 126 127 130

8.3 8.4

Chapter 9

Introduction Changing the drawing in response to user interaction 8.2.1 Drawing/erasing 8.2.2 Getting keystrokes/mouse-events 8.2.3 Double Buffering 8.2.4 The hopping frog example Continuous animation Animation changes in response to user interaction

Curves

134

9.1 9.2 9.3

134 136 137 138 141 143 143 144 146 150 150

9.4 9.5 9.6

Introduction Parametric Equations Splines 9.3.1 Representing Spline curves 9.3.2 Implementing Splines 9.3.3 Disadvantages of splines Bezier Curves 9.4.1 Representing Bezier curves 9.4.2 Implementing Bezier curves Other Curves The co-existence of multiple kinds of line

Chapter 10 3D graphics 10.1 Introduction 10.2 The 3D coordinate system 10.3 Implementing 3d - the basic 3d class structure 10.3.1 Points 10.3.2 3d Transformations 10.4 Projections - Viewing 3d on a flat screen 10.4.1 Projection - some definitions 10.4.2 Parallel projection. 10.4.3 Perspective projection 10.4.4 Oblique Parallel projections 10.4.5 Isometric Projection 10.5 Viewpoint Transformation 10.6 Implementing 3d - the data model 10.6.1 The Matrix Class 10.6.2 Point3d 10.6.3 Gitem3d 10.6.4 Line3d 10.6.5 Bezier3d 10.6.6 Transformation3d 10.7 Implementing 3d - Drawing, Projections and Viewpoints 10.7.1 Setting up the drawing transformation 10.7.2 Concatenating the transformations

154 154 154 155 155 156 161 162 163 164 165 169 170 173 175 175 176 176 176 177 179 180 180

10.7.3 Sharing the drawing transformation 10.7.4 Using the drawing transformation 10.7.5 Changing the drawing transformation

Chapter 11 Improving visual realism 11.1 Introduction 11.2 Hidden line removal 11.2.1 Determining visibility 11.2.2 Calculating the normal vector of a surface 11.2.3 An alternative approach 11.3 Implementing hidden line removal 11.4 More complex shapes 11.4.1 The Painter’s algorithm

Chapter 12 Rendering, Shading and Colour 12.1 Introduction 12.2 Illumination 12.2.1 Simplifying assumptions 12.2.2 Components of illumination 12.2.3 Diffuse illumination 12.2.4 Diffuse scattering from a point source 12.2.5 Specular reflection 12.2.6 Combining the illumination 12.2.7 Calculating E 12.2.8 Implementing illumination 12.2.9 Extending to colour 12.3 Approximating smooth surfaces with polygon nets. 12.4 Gouraud shading 12.5 Phong Shading 12.5.1 Comparison of Gouraud and Phong

Chapter 13 Fine detail - Texture Mapping and Bump Mapping 13.1 Introduction 13.2 Texture mapping 13.2.1 Modulating the colour of the surface 13.2.2 Mapping pictures onto surfaces 13.3 Tilling 13.4 Bump mapping 13.5 Some examples of texture and bump mapping

Chapter 14 Ray Tracing 14.1 Introduction 14.2 Follow a ray 14.2.1 The Ray Tree 14.3 Types of ray

181 181 184

189 189 190 193 195 198 198 203 203

210 210 211 212 213 214 215 216 218 219 221 225 226 227 231 231

235 235 237 237 238 240 240 242

245 245 246 247 247

14.3.1 Normal Rays 14.3.2 Illumination Rays 14.3.3 Diffuse Rays 14.3.4 Computing the brightness 14.3.5 Reducing Computation 14.4 Determining Intersections 14.4.1 Outline 14.4.2 Find the equation representing the light ray 14.4.3 Intersection of straight line with a plane 14.4.4 Determining where a straight line intersects a plane 14.4.5 Determining whether that point is with a given polygon 14.4.6 Applying to ray tracing

Chapter 15 Fractals 15.1 Introduction 15.2 Self similarity 15.2.1 The Koch Curve 15.2.2 The Sierpinski Gasket 15.3 The Mandelbrot Set 15.4 Utilising fractal geometry 15.4.1 Mountains 15.4.2 Trees etc. 15.5 Fractals and 3D

Chapter 16 Closing Remarks

247 248 248 248 248 248 249 250 251 251 251 253

256 256 256 257 259 262 266 266 268 271

274

12

What is “Computer Graphics”?

Chapter 1 What is “Computer Graphics”?

1.1

Introduction The field of study known simply as “Computer Graphics” is vast. It encompasses just about everything that is seen on a computer screen that isn’t a word or a number and on modern, window-based operating systems, even they will appear on screen using techniques from graphics. Table 1 - “Diversity in Computer Graphics” shows just some of thing things that could be studied under the heading of computer graphics. Software/ Hardware

Topic

Keywords

Pictures

digital drawings, digital photographs, gifs, bitmaps, jpegs, drawing tools, multimedia, digital cameras, image compression

Adobe Illustrator, Macromedia Freehand

Line drawings, Computer Aided Design (CAD), schematics, graphs, business/ presentation graphics

AutoCAD

Diagrams

Table 1. Diversity in Computer Graphics

Examples Figure 1 - “Digital photographs”

Adobe Photoshop, Paintshop Pro

MS Powerpoint

Figure 2 - “Line drawings and schematics”

13

What is “Computer Graphics”?

Software/ Hardware

Topic

Keywords

Video

.mov. avi, Quicktime, DV (digital video), video cameras

Adobe Premier

Figure 3 - “Digital Video”

Graphical User

Windows, icons, menu, pointers - (WIMPS)

Windows, Xwindows, Mac

Figure 4 - “Graphical User Interfaces”

3D Modelling

coordinate systems, transformations, clipping wireframe drawings, shading, hidden line removal, projections, rendering, ray tracing

Autocad AC3D, POV, Renderman, Alias Sketch, Lightwave

Figure 5 - “3D Modelling - Images courtesy of Autodesk(top) and BBC (bottom)”

Virtual Reality

Real-time 3D modelling, interaction, immersive/nonimmersive VR

Virtual Reality Markup Language (VRML).

Figure 6 - “Virtual Reality - (image courtesy of the Irish Tourist Board)”

Animation

Frames, tweening, frame rates

Macromedia Director

Figure 7 - “Animation - a frame from Highnoon - an animated 3D “cartoon” - Image courtesy of Michael G. Turner)”

Image Processing

filters, image enhancement, medical imaging, astronomy, maximum entropy techniques,

Khorus

Figure 8 - “Image Processing- a “raw” and processed brain scan”

Image Analysis

Edge detection, histogramming,

Khorus

Figure 9 - “Image Analysis - street scene and edgedetection”

Image Comprehension

image recognition, pattern recognition, face recognition, neural networks

(still experimental)

Graphics Hardware

display devices, VDUs, LCDs, plasma screens, TFT, goggles, VR headsets, Projection systems, interaction devices (mouse, bat, light pen, digitising pad, graphics tablets, pantograph)

Interfaces (GUIs)

Examples

Table 1. Diversity in Computer Graphics The following figures (1-9) show some examples of the various things that pass as “Computer Graphics.”

What is “Computer Graphics”?

Figure 1. Digital photographs

14

15

What is “Computer Graphics”?

library

book

author

Figure 2. Line drawings and schematics

What is “Computer Graphics”?

Figure 3. Digital Video

16

What is “Computer Graphics”?

Figure 4. Graphical User Interfaces

17

What is “Computer Graphics”?

Figure 5. 3D Modelling - Images courtesy of Autodesk1(top) and BBC2 (bottom)

1. www.autodesk.com 2. www.bbc.co.uk

18

What is “Computer Graphics”?

19

The same techniques that let architectural clients see a building that hasn’t even been built yet allow the creation of the “impossible” such as this extinct creature from the BBC series “Walking with Beasts”

Figure 6. Virtual Reality - (image courtesy of the Irish Tourist Board1)

1. www.ireland.travel.ie

What is “Computer Graphics”?

20

Figure 7. Animation - a frame from Highnoon - an animated 3D “cartoon” Image courtesy of Michael G. Turner)

Figure 8. Image Processing- a “raw” and processed brain scan

What is “Computer Graphics”?

21

Figure 9. Image Analysis - street scene and edge-detection This book concentrates on the terms and techniques that underlie the topics of diagrams, 3d-modelling, virtual reality and animation.

What is “Computer Graphics”?

22

Chapter review - 1 There’s not a great deal to summarise so far but you seen examples of the great variety of topics that make up the study of computer graphics

What is “Computer Graphics”?

23

Exercises - 1 1.1

Surf the WWW. Find examples of the different type of computer graphics applications mentioned in Chapter 1

1.2

Look at: •www.cs.strath.ac.uk/~if/classes/52359 •www.streetmap.co.uk •www.sat.dundee.ac.uk/ •www.wunderground.com/sky/ShowSky.asp?The-

Lat=52.200001&TheLon=0.180000&TimeZoneName=Europe/ London •javaboutique.webdeveloper.com/Mandelbrot/ •www.vrml.org •cgw.pennnet.com/home.cfm - the gallery section is particularly

well worth seeing. •www.computer.org/cga/

Concepts, Terms and Definitions

24

Chapter 2 Concepts, Terms and Definitions

2.1

Introduction Before examining to different specialisms that make up computer graphics, this chapter provides a quick review of the basic terms and definitions that are common to most areas of computer graphics.

Concepts, Terms and Definitions

2.2

25

Low level concepts All of computer graphics is based upon the properties of the screen or display device (more about this in Chapter ##). The fundamental thing that you need to know about displays is that they are divided into lots of small squares called pixels (“PICture ELements”).

Figure 10. Pixels

Concepts, Terms and Definitions

26

The simplest way to think of how this works is to stick to black and white. Each pixel can be set to black or white (i.e. turned on or off). This allows patterns of dots to be created on the screen.

Figure 11. Smiley pixels 2.2.1

Memory Mapping Drawing on the screen is therefore simply a matter of setting the right pixels either on or off. Each pixel on the screen corresponds to an address in the computers memory - this is known as memory mapping and the display is said to be a “memory mapped display.” Effectively, each pixel is numbered sequentially.

27

Concepts, Terms and Definitions

Memory 0

off

1

off

2

off

42

off

60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79

42

off

80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

44

ON

100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119

45

ON

140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159

46

off

160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179

47

off

180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199

48

off

220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239

49

ON

240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259

50

ON

260 261 262 263 264 265 266 267 268 269 720 271 272 273 274 275 276 277 278 279

51

off

Screen 0

1

2

3

4

5

6

7

8

9

10 11 12 13 14 15 16 17 18 19

20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59

120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139

200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219

280 281 282 283 284 285 286 297 288 289 290 291 292 293 294 295 296 297 298 299

296 off 297 off 298 off 299 off

Figure 12. Memory mapping By writing values to the correct locations in memory (this used to be called “poking the address”) the appearance of the screen can be controlled by a programmer. Conversely, by inspecting the contents of a memory location (“peeking”), a program can find out if a pixel is turned on or off. Aside

The portion of memory that is associated with the display is known as the “video memory”. In the PC architecture, this memory is usually physically located on the graphics card, which is why you can buy 8Mb graphics cards, 16Mb graphics cards etc.

Concepts, Terms and Definitions

2.2.2

28

Resolution The screen in this example is composed of 300 pixels arranged in 15 rows of 20. It is said to have a resolution of “20 by 15” (20 x 15). This is actually very small by today’s standards. Typically a display will have a resolution of 1024x768 maybe even more.

2.2.3

Screen size Resolution is NOT the same thing as screen size. Our 20x15 display could have been 5cm across (as a mobile phone display), 40cm across (as a monitor) or 20m (as a projection system) but the resolution would always be 20x15.

Figure 13. Screen size is different to resolution

29

Concepts, Terms and Definitions

2.2.4

Coordinates Whilst the computer “thinks” of its display as a simple list of addresses, it is much more convenient (for reasons which will become clear later) for us to think in terms of coordinates and leave it to the computer to convert the coordinates to memory location itself. X 0

1

2

3

4

5

6

7

8

9 10 11 12 13 14 15 16 17 18 19

0 1 2 3 4 Y

5 6 7 8 9 10 11 12 13 14

Xsize = 20 (columns) Ysize = 15 (rows)

Figure 14. Coordinates Each pixel can be referred to by a pair of numbers known as its coordinates - an x coordinate (which gives the column’s number and a y coordinate which gives the row number. The coordinates are always written as a pair of numbers, separated by a comma and enclosed in brackets. This system of geometry is known as “Cartesian geometry” and the coordinates are spoken of as being “Cartesian Coordinates.”

Example 1. Simple coordinate example

In Figure 14 - “Coordinates” - pixel (7,12) is set ON. Aside

The computer converts coordinates to memory addresses by subtracting the y coordinate from the number of rows on the display minus 1 (i.e. Ysize -1, this effectively turns the y axis upside-down to make sure that the origin is at the bottom), multiplying that by the number of columns on the display

30

Concepts, Terms and Definitions

(Xsize), and adding the x coordinate. location = (((Ysize - y)* Xsize) + x).

(Equation 1)

(7,12) becomes (((14-12)*20) + 7) = 47 2.2.5

The origin In this example, the origin or (0,0) is in the bottom-left of the screen. It is also possible to have the origin in the top-left, in which case the rows (y coordinates) are numbered downwards

2.2.6

Colour In discussion so far, we have been restricted to black and white (monochrome) i.e. each pixel can only be on or off. Colour can be represented in most of today’s computers. Typically, instead of each pixel being represented by one bit (on or off) each pixel will be represented by 24 bits - 3 x 8 bit bytes. Each byte represents a number between 0 and 255 and each byte is associated with one primary colour - red, blue, green. red 0000 0000

green

blue

0000 0000

1111 1111

binary

0

0

255

decimal

00

00

FF

hex

1100 0000

1100 0000

0000 0000

binary

192

192

0

decimal

C0

C0

00

hex

Figure 15. 24 bit colour representation

31

Concepts, Terms and Definitions

Memory 0

FF FF FF

1

FF FF FF

2

FF FF FF

42

FF FF FF

60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79

42

FF FF FF

80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

44

FF 00 00

100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119

45

FF 00 00

140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159

46

FF FF FF

160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179

47

FF FF FF

180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199

48

FF FF FF

220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239

49

FF 00 00

240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259

50

FF 00 00

260 261 262 263 264 265 266 267 268 269 720 271 272 273 274 275 276 277 278 279

51

FF FF FF

Screen 0

1

2

3

4

5

6

7

8

9

10 11 12 13 14 15 16 17 18 19

20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59

120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139

200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219

280 281 282 283 284 285 286 297 288 289 290 291 292 293 294 295 296 297 298 299

296 FF FF FF 297 FF FF FF 298 FF FF FF 299 FF FF FF

Figure 16. Colour smiley

Concepts, Terms and Definitions

2.2.7

32

Image files From Figure 16 - “Colour smiley” it is hopefully only a small step to see how Figure 17 - “smiley.bmp” works. The pattern of bits in the computer’s memory forms what is know as a bitmap. 7 9

44 45 46 47 48 49 50

FF 00 00 FF 00 00 FF FF FF FF FF FF FF FF FF FF 00 00 FF 00 00

64 65 66 67 68 69 70

FF 00 00 FF 00 00 FF FF FF FF FF FF FF FF FF FF 00 00 FF 00 00

84 85 86 87 88 89 90

FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF

104 105 106 107 108 109 110

FF FF FF FF FF FF FF FF FF 00 FF 00 FF FF FF FF FF FF FF FF FF

124 125 126 127 128 129 130

FF FF FF FF FF FF FF FF FF 00 FF 00 FF FF FF FF FF FF FF FF FF

144 145 146 147 148 149 150

FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF

164 165 166 167 168 169 170

FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF

184 185 186 187 188 189 190

FF FF FF 00 00 FF FF FF FF FF FF FF FF FF FF 00 00 00 FF FF FF

204 205 206 207 208 209 210

FF FF FF FF FF FF 00 00 FF 00 00 FF 00 00 FF FF FF FF FF FF FF

Figure 17. smiley.bmp By simply writing those values out to a data file and prepending information about the height and depth of the image, a picture can be saved onto disk and this is indeed the approach taken by all the common graphical formats seen on the WWW such as.gifs,.jpegs, etc.

Concepts, Terms and Definitions

33

It is then an even smaller step (increase the number of pixels (i.e the resolution)) to see how Figure 18 - “Carol Smiley” works.

Figure 18. Carol Smiley 1

2.3

2D Drawing Leaving image processing behind for now, we can concentrate on the basic terminology associated with drawing on a computer screen. In two dimensions (2D), drawing are made up of points, lines and shapes.

2.3.1

Points Representing a point on a 2d drawing to a computer is simple. In fact you’ve already seen it, we just use coordinates.

1.

- Image courtesy of the BBC - www.bbc.co.uk

34

Concepts, Terms and Definitions

12

7 14

Y

13 12 11 10 9 8 7 6 5 4 3 2 1 0 0

1

2

3

4

5

6

7

8

9 10 11 12 13 14 15 16 17 18 19 X

Figure 19. Using coordinates to represent points Aside

It is worth noting at this stage two important points about the difference between coordinates that you will have come across in a maths course and those used by computers: 1. At the lowest level, a computer only understand integers as coordinates. If you want to plot a point at (5.673,48.32795) you will need some scheme for converting it sensibly into an integer value. 2. In maths, a point has no size, it is infinitesimally small. To a computer, a point is the size of a pixel.

35

Concepts, Terms and Definitions

2.3.2

Lines A (straight) line can be mathematically defined by its end points. To describe the line in figure X we need simply to state the coordinates of the two ends: (3,8),(12,2)

8

2

3

12

Figure 20. Defining a line using the start and end points (3,8),(12,2) Aside

Another difference between raw maths and computer graphics, a line has no thickness, in computer graphics, it does. 2.3.3

Shapes At this stage all you need to know is that shapes are made up of collections of lines.

Concepts, Terms and Definitions

36

Chapter review - 2 So far, you should have learned about: •screens being divided into pixels, resolution and size •memory mapping - whereby each pixel has a corresponding

address in video memory which can be set or read to set or interrogate the colour of the pixel •the 24 bit colour representation scheme •the concept of ‘bitmaps’ whereby patterns in memory can represent the appearance of the screen •a simple image file format •the representation of point and lines by means of cartesian coordi-

nates

Concepts, Terms and Definitions

37

Exercises - 2 2.1

Find out the size, resolution and colour depth of the display you are using.

2.2

Describe the difference between size and resolution

2.3

What steps must a computer take to plot a point (22.25,10.4)?

38

A first graphics program

Chapter 3 A first graphics program

3.1

Introduction It is traditional, when learning to program, that you start with a “Hello World” program. In this chapter, we will look at the graphics equivalent of that textual tradition. The code for this is below in example 2.

Example 2. Apollo 13

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

//-----------------------------------------------// // // File : Gapp.java // // //-----------------------------------------------import java.awt.*; import java.awt.event.*; import javax.swing.*;

public class Gapp extends JFrame

{

public Gapp(){ setBackground(Color.white); }// constructor

public void initComponents() throws Exception { setLocation(new java.awt.Point(0, 30));

A first graphics program

23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58

1 2 3 4 5 6 7 8 9 10 11 12 13 14

39

setSize(new java.awt.Dimension(350, 400)); setTitle("Graphics Application"); getContentPane().add(new Gcanvas()); addWindowListener(new java.awt.event.WindowAdapter() { public void windowClosing(java.awt.event.WindowEvent e) { thisWindowClosing(e); } }); }//end - initComponents

void thisWindowClosing(java.awt.event.WindowEvent e){ // Close the window when the close box is clicked setVisible(false); dispose(); System.exit(0); }//end - thisWindowClosing

static public void main(String[] args) { // Main entry point try { Gapp myGapp = new Gapp(); myGapp.initComponents(); myGapp.setVisible(true); } catch (Exception e) { e.printStackTrace(); } }//end main }//end of class -- Gapp -------------------------------------------

import java.awt.*; import javax.swing.*; //--------------------------------------------------------------// // // File : Gcanvas.java // // //---------------------------------------------------------------class Gcanvas extends JPanel { public void paintComponent(Graphics g){ g.drawLine(50,50,50,150); g.drawLine(50,150,100,100);

40

A first graphics program

15 16 17 18 19 20 21 22 23 24 25

g.drawLine(100,100,100,150); g.drawLine(100,150,200,150); g.drawLine(200,150,200,50); g.drawLine(200,50,100,50); g.drawLine(100,50,100,100); g.drawLine(100,100,50,50); g.drawLine(200,50,250,100); g.drawLine(250,100,200,150); }//paintComponent }//end of -- Gcanvas --------------------------------------------

Running this program should result in the output found in Figure 21

Figure 21. Apollo 13 in flight

3.2

The features of a simple graphics program You can’t have it both ways with a programming language, it can either be simple or powerful. Java most definitely falls in the powerful (consequently complex) category. In order to draw things on screen (which is what this is all about) you have to have quite a bit of Java “machinery” in place before you can start. Let’s look at this machinery first via the simplest Java graphics

41

A first graphics program

program I could write. This program (let’s call it Apollo13) consists of two Java classes - Gapp (graphics application) and Gcanvas. Figure 22 shows a UML diagram of the program.

JDK - swing JFrame

JPanel

application

ours Gapp initComponents thisWindowclosing

Gcanvas paintComponent

Figure 22. Design diagram for a simple graphics application Gapp and Gcanvas are ours - we’re developing them. JFrame and JPanel from which they inherit, are part of the Java Foundation Classes - in particular they are part of the “swing” GUI components.

42

A first graphics program

The class Gapp inherits from JFrame, so when a Gapp object is created, a new GUI window appears on the screen. A Gcanvas/JPanel is simply something we can draw on, but it isn’t allowed to be on screen by itself, it must be part of a JFrame. So what we actually need is an instance of Gapp with an instance of Gcanvas “inside” it (Figure 23). application

Figure 23. Gapp with a Gcanvas in it The main entry point for the system is in class Gapp (line 46) - the main function, simply creates a Gapp object (line 49), and calls its initComponents() method(50). initComponents() sets the window’s on-screen position and size (lines 22,23), sets up some Java apparatus to allow the program to shut down cleanly (lines 27-31) and creates an instance of a Gcanvas (graphics canvas) - line 25. This GCanvas object is “added” to the Gapp object and hence becomes part of that window. The actual interesting bit of this program occurs in method paintCompoin file Gcanvas.java (lines 12-23). Here we finally see how to draw lines in Java. When paintComponent() is called, it is passed a Graphics object (“g”) - line 12. We use a method called drawLine() which is a member of the Graphics class. The Graphics class is part of the standard Java class library and its member functions are termed “graphics primitives”. There exist a whole range of methods for drawing and colouring in a variety of shapes (boxes, polygons, ovals etc.) Take a look at the Java API documentation for full details.

nent()

The drawLine() method takes 4 parameters, x1, y1, x2, y2 which represent the start and end points of the line you wish to draw. “But......?” you may be asking, this program doesn’t actually seem to call the paintComponent() method and where does it get “g” the Graphics object from? You are right, it doesn’t. paintComponent() is what is known as a “callback” method. It’s actually called by the system every time the JPanel object it belongs to needs to be displayed on screen and likewise the system passes us the mysterious “g” to provide all of those useful functions - we don’t have to worry about it.

43

A first graphics program

So how is Apollo 13 actually drawn? The drawing is simply broken down into a series of lines. Each line has a start point and an end point. These points are expressed in terms of there coordinates and by cross referencing between Figure 24 and lines 13-22 of Gcanvas.java you can see how the drawing is built up. 0

50

(50,50)

100

150

(100,50)

250

200

x

(200,50)

50

(100,100)

100

150

(50,150)

(100,150)

(250,100)

(200,150)

y

Figure 24. The Coordinates of Apollo 13

3.3

Organising your work for Java Here are some hints and tips more minimising hassle while trying to get the examples and exercises working. •Make a directory somewhere called “graphics” •Do each example/exercise in a separate sub-directory of graphics. •make sure you have “.” on your classpath •UNIX

- setenv JAVA_HOME /usr/local/jdk1.3/bin - setenv CLASSPATH "$JAVA_HOME":. •WIN - set JAVA_HOME=C:\jdk1.3\bin - set CLASSPATH=%JAVA_HOME%;.

44

A first graphics program

3.4

Graphics Primitives A graphics primitive can be loosely defined as a drawing function which the system makes available to the applications programmer. An obvious example is the Java “drawLine()” method. The subject of what to include within a system as a primitive can get quite complex because consideration needs to be given to the properties of the display device. Display devices include computer screens, printers, plotters and 'barco' type scanned optical displays. Even within a display device type there may be several alternative forms (e.g. a 'computer screen may be a CRT vector system, a CRT raster system or a LCD. A printer may be dot matrix, ink jet, laser etc.). At the lowest level each device needs its own set of primitives (hardware primitives). For a raster type display at the lowest level we only need to implement a pixel(x,y) on - off function. We normally expect to work at a higher level with a more comprehensive set of primitives which could, for example, include: Line, Circle (ellipse?), box, polyline, arc, fill etc. Java implements a comprehensive set of graphics primitives: abstract void clearRect(int x, int y, int width, int height)

Clears the specified rectangle by filling it with the background color of the current drawing surface.

abstract void clipRect(int x, int y, int width, int height)

Intersects the current clip with the specified rectangle.

abstract void copyArea(int x, int y, int width, int height, int dx, int dy)

Copies an area of the component by a distance specified by dx and dy.

abstract

Creates a new Graphics object that is a copy of this Graphics object.

Graphics create()

Graphics create(int x, int y, int width, int height)

Creates a new Graphics object based on this Graphics object, but with a new translation and clip area.

abstract

void dispose()

Disposes of this graphics context and releases any system resources that it is using.

void draw3DRect(int x, int y, int width, int height, boolean raised)

Draws a 3-D highlighted outline of the specified rectangle.

Table 2. The methods of the Graphics class - taken from the on-line Java JDK manuals

45

A first graphics program

abstract void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle)

Draws the outline of a circular or elliptical arc covering the specified rectangle.

void drawBytes(byte[] data, int offset, int length, int x, int y)

Draws the text given by the specified byte array, using this graphics context's current font and color.

void drawChars(char[] data, int offset, int length, int x, int y)

Draws the text given by the specified character array, using this graphics context's current font and color.

abstract boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer)

Draws as much of the specified image as is currently available.

abstract boolean drawImage(Image img, int x, int y, ImageObserver observer)

Draws as much of the specified image as is currently available.

abstract boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer)

Draws as much of the specified image as has already been scaled to fit inside the specified rectangle.

abstract boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer)

Draws as much of the specified image as has already been scaled to fit inside the specified rectangle.

abstract boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer)

Draws as much of the specified area of the specified image as is currently available, scaling it on the fly to fit inside the specified area of the destination drawable surface.

abstract boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer)

Draws as much of the specified area of the specified image as is currently available, scaling it on the fly to fit inside the specified area of the destination drawable surface.

abstract void drawLine(int x1, int y1, int x2, int y2)

Draws a line, using the current color, between the points (x1, y1) and (x2, y2) in this graphics context's coordinate system. Draws the outline of an

abstract void drawOval(int x, int y, int width, int height)

oval.

abstract void drawPolygon(int[] xPoints, int[] yPoints, int nPoints)

Draws a closed polygon defined by arrays of x and y coordinates.

Table 2. The methods of the Graphics class - taken from the on-line Java JDK manuals

46

A first graphics program

void drawPolygon(Polygon p)

Draws the outline of a polygon defined by the specified Polygon object.

abstract void drawPolyline(int[] xPoints, int[] yPoints, int nPoints)

Draws a sequence of connected lines defined by arrays of x and y coordinates.

void drawRect(int x, int y, int width, int height)

Draws the outline of the specified rectangle.

abstract void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)

Draws an outlined roundcornered rectangle using this graphics context's current color.

abstract void drawString(AttributedCharacterIterator iterator, int x, int y)

Draws the text given by the specified iterator, using this graphics context's current color.

abstract void drawString(String str, int x, int y)

Draws the text given by the specified string, using this graphics context's current font and color.

void fill3DRect(int x, int y, int width, int height, boolean raised)

Paints a 3-D highlighted rectangle filled with the current color.

abstract void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle)

Fills a circular or elliptical arc covering the specified rectangle.

abstract void fillOval(int x, int y, int width, int height)

Fills an oval bounded by the specified rectangle with the current color.

abstract void fillPolygon(int[] xPoints, int[] yPoints, int nPoints)

Fills a closed polygon defined by arrays of x and y coordinates.

void fillPolygon(Polygon p)

Fills the polygon defined by the specified Polygon object with the graphics context's current color. Fills the specified rectan-

abstract void fillRect(int x, int y, int width, int height)

gle.

abstract void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)

Fills the specified rounded corner rectangle with the current color.

void finalize()

Disposes of this graphics context once it is no longer referenced.

Table 2. The methods of the Graphics class - taken from the on-line Java JDK manuals

47

A first graphics program

abstract

Gets the current clipping

Shape getClip() area.

abstract

Rectangle getClipBounds()

Returns the bounding rectangle of the current clipping area.

Rectangle getClipBounds(Rectangle r)

Returns the bounding rectangle of the current clipping area.

Rectangle getClipRect()

Deprecated. As of JDK version 1.1, replaced by getClipBounds().

abstract

Color getColor()

abstract

Font getFont()

FontMetrics getFontMetrics()

Gets this graphics context's current color. Gets the current font. Gets the font metrics of the current font.

abstract FontMetrics getFontMetrics(Font f)

Gets the font metrics for the specified font.

boolean hitClip(int x, int y, int width, int height)

Returns true if the specified rectangular area intersects the bounding rectangle of the current clipping area.

abstract void setClip(int x, int y, int width, int height)

Sets the current clip to the rectangle specified by the given coordinates.

abstract

void setClip(Shape clip)

Sets the current clipping area to an arbitrary clip shape.

abstract

void setColor(Color c)

Sets this graphics context's current color to the specified color.

abstract

void setFont(Font font)

Sets this graphics context's font to the specified font.

abstract

void setPaintMode()

Sets the paint mode of this graphics context to overwrite the destination with this graphics context's current color.

abstract

void setXORMode(Color c1)

Sets the paint mode of this graphics context to alternate between this graphics context's current color and the new specified color.

String toString()

Returns a String object representing this Graphics object's value.

Table 2. The methods of the Graphics class - taken from the on-line Java JDK manuals

48

A first graphics program

abstract

void translate(int x, int y)

Translates the origin of the graphics context to the point (x, y) in the current coordinate system.

Table 2. The methods of the Graphics class - taken from the on-line Java JDK manuals Use the on-line Java JDK manuals to find details of these functions. The full documentation of this class can be found at http://java.sun.com/j2se/1.3/ docs/index.html. To overcome the dependence of primitives on the display device a variety of standard 'device independent' software graphics primitives have been defined. The three most important standards you may meet are GKS (Graphics Kernel System), PHIGS (Programmers Hierarchical Interactive Graphics System) and X-Windows. A comparison of the relative merits of the competing systems is beyond the scope of this book. For our purposes, device independence is provided by the Java runtime system. In summary, the process of generating a graphics image on the display device can be represented by the following pipeline: Figure 25 - “Programmer’s model of a computer graphics system - version 2” .

application program

Graphics Primitives

Video memory

Display

Figure 25. Programmer’s model of a computer graphics system - version 2 Applications programs rarely (if ever) access the video memory directly indeed Java has no facilities to do this. All drawing is done via the graphics primitives. Further stages in this system will be added later. Workstations which are optimised for graphics applications (e.g. Silicon Graphics) will often implement a wide range of graphics primitives in hardware thus speeding up the applications.

49

A first graphics program

Chapter review - 3 So far, you should have learned about: •the structure of a basic Java graphics program •how to draw lines in Java •what a graphics primitive is •how an application program actually draw something on screen.

50

A first graphics program

Exercises - 3 3.1

Type in the Apollo13 program and make it work.

3.2

Make a copy of the Apollo 13 program in a new directory and modify it so that the output looks like this (Hint - you only need to change one function - paintComponent in Gcanvas):.

3.3

Alter the “hi” program to say “hello world”

3.4

Square swirl - Write a Java program to draw a square and then to draw a second square within the first reduced in size by the

51

A first graphics program

parameter u (see Figure 26 below, try u = 0.1 to start with) and so on until you have drawn approximately 40 diminishing squares.

(Px,Py)

P P1 (P1x,P1y)

Q (Qx,Qy) Q1 (Q1x,Q1y)

distance PP1 distance PQ

(S1x,S1y) S1

(R1x,R1y) R1

(Sx,Sy) S

=

distance SS1 distance SP

0 < u < 1 e.g. u= 0.1 R

(Rx,Ry)

P,Q,R,S - are the vertices of the first (outer) square P1,Q1,R1,S1 - are the vertices of the second (inner) squareu

HINT P1x = ((1 - u) * Px) + (u * Qx) P1y = ((1 - u) * Py) + (u * Qy) similarly for Q,R,S

Figure 26. The swirling square problem

=u

52

A first graphics program

Your answer should look something like Figure 27

Figure 27. Square swirl 3.5

Try the same thing with other shapes, triangle etc. (no answers for this one - your on your own!)

3.6

Generalise your solution so that it will draw any n-sided regular polygon.

53

Graphics Primitives

Chapter 4 Graphics Primitives

4.1

Introduction Having seen the anatomy of a simple Java graphics program, some questions arise: How does that program relate to the concepts of pixels and memory mapping that were introduced in Chapter 2? How were the lines drawn? What exactly did the line: g.drawLine(50,50,50,150);

do? In this chapter we will “lift the bonnet” and see how drawLine(), drawOval() etc. (the Java graphics primitives) operate. You will rarely need to program at this level (routines such as these are provided so you don’t have to), but an appreciation of how Java manages to efficiently perform such operations will influence our later program design.

4.2

Drawing straight lines Let’s start with drawLine(). We’re going to develop our own version of this method and as with so many things in computing there are two ways of doing it: the brute force approach and the elegant way. We’ll tackle the brute force way first to introduce some ideas and then looks at how it can be refined to produce an efficient (i.e. quick) solution known as Bresenham’s algorithm.

54

Graphics Primitives

4.2.1

Brute force The starting point (pun intended) is the equation of a straight line: y=mx+c

(Equation 2)

where m is the gradient of the line: y to – y from ∆y m = ------ = -----------------------x to – x from ∆x and c is its intercept of the y-axis c = y from – ( m × x from )

(Equation 3)

Assume that the endpoints of the line are known: (xto,yto) ∆y c

(xfrom,yfrom)

Figure 28. Specifying straight lines

∆x

55

Graphics Primitives

For any value of x we can compute y. Fortunately, in the quantized, integer valued world of computer graphics and bitmapped displays we know all the values of x. Figure 29 shows a rather course-grained bit mapped display superimposed upon the “mathematical” straight line of the previous diagram.

xfrom, xfrom+1, xfrom+2.....xto

Figure 29. Enumerating the values of x A for loop can be used to iterate through all the values of x between xfrom and xto and for each of these, the corresponding y value calculated. for(int x= xfrom; x
In order to use Equation 2, we must have values for m and c. Since we know two points on the line, these can easily be computed. Thus, the routine (in pseudocode) to draw a line might look like this: method lineDraw(xfrom,yfrom,xto,yto){ DeltaY = yto-yfrom; DeltaX = xto-xfrom; m = DeltaY/DeltaX; c=yfrom - (m*xfrom); for(int x= xfrom; x
56

Graphics Primitives

The results of running such an algorithm look like Figure 29. Unfortunately, if we pick coordinates for the end points such that the line is steep (in fact when its gradient m is >1 the results look something like Figure 30:

Figure 30. Brute force algorithm with steep gradient Gaps start to appear in the line. Why has this happened? Looking at some values for x and y soon reveals that when m>1, the values for y increase by an amount greater than the increments of x that we are using (i.e. >1). Compare the lines xfrom = 10, yfrom = 10, xto = 40, yto = 30 (Figure 29) xfrom = 10, yfrom = 10, xto = 40, yto = 90 (Figure 30) Figure 29

Figure 30

x

y

x

y

10

10

10

10

11

11

11

13

12

11

12

15

13

12

13

18

14

13

14

21

Table 3. Comparison of x & y values for different gradients

The solution to the gaps problem is to always use the most rapidly changing variable (x or y) as the index to the loop (Figure 31). i.e.When the gradient (m) >1 - use y as the control variable in the loop and make x the subject of the equation: x = (y-c)/m

(Equation 4)

57

Graphics Primitives

m=1 use y use x

use x use y m = -1

Figure 31. Control variable usage depend on line gradient There is a second problem with the brute force approach: It requires floating point arithmetic which is slow when compared with using integer only arithmetic. An approach which used solely integers would result in a much quicker algorithm.

58

Graphics Primitives

4.2.2

Bresenham’s algorithm One such integer only algorithm is Bresenham’s. Start by considering the simple case where 0 < m <1

yi+1 yreal yi

∆y1 ∆y0

xi xi+1

Figure 32. The basis of Bresenham’s algorithm Consider, as before, iterating the x values from left to right. If the pixel at (xi,yi) has been plotted, then the next one MUST be either (xi,yi) or (xi,yi+1). Why? - because of the gradient - it can’t go up more than one step in the y direction for one step in the x (m<1) and it can’t go down at all (0<=m). How can we work out which pixel to plot? - by calculating the difference between the true mathematical value of y at xi+1 (we’ll call that yreal) and the y values represented by the pixels. ∆y1 and∆y0 present these distances and the decision on which pixel to plot can be made by following the following pseudocode if

∆y0 > ∆y1

then

plot pixel at yi+1 else plot pixel at yi

How then are ∆y1 and∆y0 calculated? ∆y1 = (yi +1) - yreal = (yi +1) - m(xi+1) -c

59

Graphics Primitives

∆y0 = yreal - yi = (m(xi+1) + c) - yi Subtracting the two expressions gives: ydiff = ∆y0 - ∆y1 = 2m(xi+1) + 2c -2yi -1

if

ydiff > 0

(Equation 5)

then

plot pixel at yi+1 else plot pixel at yi

At this point, we’re still using m which is a real number, but m = ∆y/∆x substituting this into Equation 5 gives: Di = ∆x(∆y0 − ∆y1) = 2∆y.xi - 2∆x.yi + (2∆y + ∆x(2c - 1))

(Equation 6)

This must always be the same sign as ydiff because ∆x is always +ve (we’re going left to right remember. Call it Di - the decision criteria. Simplify Equation 6 by collecting together all the non-index terms and the real terms into one term - K K = 2∆y + ∆x(2c - 1)

(Equation 7)

Di = ∆x(∆y0 − ∆y1) = 2∆y.xi - 2∆x.yi + K

(Equation 8)

Now only K contains a non-integer term. We can make that term disappear by calculating Di+1 from Di 2Δy.xi+1 - 2Δx.yi+1 + K

Di +1 = 2∆y.xi - 2∆x.yi + c

(Equation 9)

Subtract Equation 8 from Equation 9: Di +1 - Di = 2∆y.(xi+1 - xi) - 2∆x.(yi+1 - yi) But, xi+1 = xi+1 and we know either: yi+1 = yi

(Equation 10)

60

Graphics Primitives

or yi+1 = yi +1 so Di +1 = Di + 2∆y - 2∆x(yi+1 - yi) We know that the first value of y (y0) from the starting coordinates of the line. The initial value of D (which is called Do) can be derived thus: c = y0 - mxo = y0 - (∆y/∆x)x0 Do = 2∆y - ∆x 4.2.3

An implementation of the line drawing algorithms import java.awt.*; import javax.swing.*; //---------------------------------------------------------------// // // File : Gcanvas.java // // //----------------------------------------------------------------

class Gcanvas extends JPanel { int int int int int

deltaX; deltaY; DY2; DX2; Di;

public void bresenhamLine(int x1, int y1, int x2, int y2, Graphics g){ deltaX = x2-x1; deltaY = y2-y1; DY2 = 2* deltaY; DX2 = 2* deltaX; Di = DY2 - deltaX; int x = x1; int y = y1; int prevy; while (x
61

Graphics Primitives

if (Di > 0){ y++; } g.drawLine(x,y,x,y); Di = Di + DY2 - (DX2 * (y - prevy)); }//while }//bresenhamLine

public void bruteLine(int x1, int y1, int x2, int y2, Graphics g){ int deltaX = x2-x1; int deltaY = y2-y1; float m = (float)deltaY/(float)deltaX; float c = y1 - (m*x1); for (int x=x1; x
public void paintComponent(Graphics g){ bruteLine(10,10,40,30,g); bruteLine(10,10,40,90,g); bresenhamLine(50,50,150,60,g); bresenhamLine(50,50,150,120,g); bresenhamLine(50,50,150,140,g); bresenhamLine(50,50,150,200,g);// this one will produce wrong line as its outside of gradient range.

}//paintComponent

}//end of -- Gcanvas -------------------------------------------

62

Graphics Primitives

Figure 33. The output of the line drawing program

4.3

Circles Several approaches are possible to the problem of getting a computer to draw a circle. One, which we shall again label the “brute force” algorithm works but is computationally intensive, a second (the Digital Differential Analyzer) dispenses with the need for sine and cosine tables, another, again due to Bresenham, is elegant, efficient and not at all obvious. All approaches are based on the equation of a circle (Equation 11) and its parametric forms (Equation 12 and Equation 13). For a circle centred on the origin: x2 + y2 = R2

(Equation 11)

x = R sin θ

(Equation 12)

y = R cos θ

(Equation 13)

63

Graphics Primitives

R θ x

y

Figure 34. The equation of a circle 4.3.1

Brute force It’s simple to write a piece of code (see Example 4 below) which iterates many values of θ, directly calculates the x and y values and then plots points at those coordinates. Regrettably, this is extremely inefficient and also relies on the availability of sine and cosine tables.

4.3.2

Cheating with 8 arcs Some saving of calculation (and hence time) can be made by using the symmetry of the circle. Suppose you have calculated some value for x and y and plot the appropriate point (x,y). The points (x,-y), (-x,-y), (-x,y), (y,x), (y,-x), (-y,-x) and (-y,x) will also lie on the circle and can also be plotted. (y,x)

(-y,x) (-x,y)

(x,y) O (x,-y)

(-x,-y) (-y,-x)

(y,-x)

Figure 35. Efficiency via cheating The following Java code implements the algorithm.

64

Graphics Primitives

26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63

public void eightPlot(int x, int y, int xOffset, int yOffset, Graphics g){ int xPlot = Math.round(xOffset + x); int yPlot = Math.round(yOffset + y); g.drawLine(xPlot,yPlot,xPlot,yPlot); xPlot = Math.round(xOffset + x ); yPlot = Math.round(yOffset - y); g.drawLine(xPlot,yPlot,xPlot,yPlot);

xPlot = Math.round(xOffset - x); yPlot = Math.round(yOffset - y); g.drawLine(xPlot,yPlot,xPlot,yPlot);

xPlot = Math.round(xOffset - x); yPlot = Math.round(yOffset + y); g.drawLine(xPlot,yPlot,xPlot,yPlot); xPlot = Math.round(xOffset + y); yPlot = Math.round(yOffset + x); g.drawLine(xPlot,yPlot,xPlot,yPlot); xPlot = Math.round(xOffset + y ); yPlot = Math.round(yOffset - x); g.drawLine(xPlot,yPlot,xPlot,yPlot);

xPlot = Math.round(xOffset - y); yPlot = Math.round(yOffset - x); g.drawLine(xPlot,yPlot,xPlot,yPlot);

xPlot = Math.round(xOffset - y); yPlot = Math.round(yOffset + x); g.drawLine(xPlot,yPlot,xPlot,yPlot); }//eightPlot

Example 3. eightPlot()

65

Graphics Primitives

The use of this symmetry means that values for x and y only need to be calculated for the octant where 0 < x <= y (the arc indicated above). Start with x= radius and y=0. Iterate until y=x. As you calculate and plot more points, 8 arcs “grow” and eventually meet to form the circle.

Figure 36. 8 arcs a-growing 4.3.3

Digital Differential Analysis - An incremental algorithm A slightly better (quicker) way of calculating x and y values is the “digital differential analyzer” algorithm which doesn’t require sine and cosine tables but instead is based upon calculating the gradient of a circle at a point and using that to approximate the position of the next point. Our starting point, is the equation of a circle (of radius R) centred on the origin: x2 + y2 = R2

(Equation 14)

Re-arranging, making y the subject gives: y =

2

R –x

2

(Equation 15)

By differentiating this with respect to x, we find that the gradient (m) at any point on the circle is dy ------ = –-----x = m dx y

(Equation 16)

66

Graphics Primitives

Now consider Figure 37: circle true

( T xi + 1, T yi + 1 )

approximate

T A ( A xi + 1, A yi + 1 ) ε

∆y

∆x (xi,yi)

pixel boundaries

Figure 37. Basis of the incremental algorithm Assume that the values of xi,yi for one point of the circle are known (given). From them we would ideally like to calculate the values of x and y at Point T (the true values for x and y at the next point round the circle). In fact, we can calculate the x and y values at Point A quickly and by making sure than the distance between successive points (ε) is small, then Point A will lie close to the true circle. Equation 16 gives us the gradient (m) of a tangent to the circle, but the gradient of that tangent is also: A yi + 1 – y i ∆y ------ = -------------------- = m ∆x A xi + 1 – x i

(Equation 17)

For small increments (ε) points T and A are close. i.e. εx i ∆y T yi + 1 – y-i –--------. ------ ≈ -------------------≈ ∆x T xi + 1 – x i εy i

(Equation 18)

This is a bit of a cheat to get ε into the equation, but since it appears in both numerator and denominator it is obviously true. Thus: A xi + 1 = x i + εy i

(Equation 19)

A yi + 1 = y i – ε x i

(Equation 20)

and

We can chose the value of ε, so by choosing a small value, then Axi+1,Ayi+1 will be (almost) on the circle.

67

Graphics Primitives

The value for ε must be selected to produce a change in x and y of around 1 pixel at a maximum. In practise this can be achieved by selecting ε such that: 1 n–1 n ε = ----n- where 2 ≤R<2 2 The DDA can be combined with our eightPlot() method to reduce still further the number of calculations required. As well as being “cool” (or “an elegant solution” if we’re being more formal), this method is a much more efficient way of calculating the points of a circle that the brute force approach. It still, however, contains one floating point division (to work out -x/y) for each point plotted. For absolute efficiency, we need a totally integer based solution and must turn once again to the work of Bresenham. 4.3.4

Bresenham’s algorithm for circles Like Bresenham’s line algorithm, this approach is based on limiting the choice of the next pixel to be plotted to two alternatives, then creating and testing a decision variable to determine which alternative is actually plotted. Whereas Bresenham’s line algorithm calculated the “errors” ∆y1 and ∆y0 based on the straight line equation (Equation 2), here we use the equation of a circle - Equation 14 2

2

D ( Si ) = [ ( xi – 1 + 1 ) + ( yi – 1 ) ] – R 2

2

2

D ( Ti ) = [ ( xi – 1 + 1 ) + ( yi – 1 – 1 ) ] – R

(Equation 21)

2

(Equation 22)

68

Graphics Primitives

D(Si) and D(Ti) represent the differences between the squared distance between the centre of the circle and the middle of pixels S and T . Whichever is smallest corresponds to the pixel that should be plotted.

S T

D ( Ti )

S T

D ( Si )

Figure 38. Distances in the Bresenham’s circle algorithm To combine the two calculations into one “decision variable” is simply a matter of subtracting the two expressions: di = |D(Si)| - |D(Ti)|

(Equation 23)

69

Graphics Primitives

We can then make the decision based upon whether di is positive or negative: if

di > 0

then

plot pixel T else plot pixel S

Actually, because the decision is based purely upon the sign of di (and not its value), Equation 23 can be simplified to di = D(Si) +D(Ti)

(Equation 24)

After some re-arrangement (which is left as a exercise for the reader) the following can be obtained: di = 3 - 2R

(Equation 25)

If pixel S is chosen (based upon the sign of di in Equation 25 then di+1 = di +4xi-1 + 6

(Equation 26)

or if pixel T is chosen di+1 = di + 4(xi-1 -yi-1) + 10 4.3.5

(Equation 27)

An implementation of the circle drawing algorithms The code in Example 4 implements the algorithms described above. Only the Gcanvas class is shown. The resulting output is shown in Figure 39. import java.awt.*; import javax.swing.*; //---------------------------------------------------------------// // // File : Gcanvas.java // // //---------------------------------------------------------------class Gcanvas extends JPanel {

public void eightPlot(int x, int y, int xOffset, int yOffset, Graphics g){ int xPlot = Math.round(xOffset + x); int yPlot = Math.round(yOffset + y); g.drawLine(xPlot,yPlot,xPlot,yPlot);

70

Graphics Primitives

xPlot = Math.round(xOffset + x ); yPlot = Math.round(yOffset - y); g.drawLine(xPlot,yPlot,xPlot,yPlot); xPlot = Math.round(xOffset - x); yPlot = Math.round(yOffset - y); g.drawLine(xPlot,yPlot,xPlot,yPlot); xPlot = Math.round(xOffset - x); yPlot = Math.round(yOffset + y); g.drawLine(xPlot,yPlot,xPlot,yPlot); xPlot = Math.round(xOffset + y); yPlot = Math.round(yOffset + x); g.drawLine(xPlot,yPlot,xPlot,yPlot); xPlot = Math.round(xOffset + y ); yPlot = Math.round(yOffset - x); g.drawLine(xPlot,yPlot,xPlot,yPlot); xPlot = Math.round(xOffset - y); yPlot = Math.round(yOffset - x); g.drawLine(xPlot,yPlot,xPlot,yPlot); xPlot = Math.round(xOffset - y); yPlot = Math.round(yOffset + x); g.drawLine(xPlot,yPlot,xPlot,yPlot); }//eightPlot

public void bruteCircle(int x0, int y0, int radius, Graphics g){ for(int theta=0; theta<45; theta++){ int x = Math.round(radius * (float)Math.sin(Math.toRadians(theta))); int y = Math.round(radius * (float)Math.cos(Math.toRadians(theta)));

eightPlot(x,y,x0,y0,g); // int xPlot = x0 + x; // int yPlot = y0 + y; // g.drawLine(xPlot,yPlot,xPlot,yPlot); //plot a point @ (xPlot,yPlot) } }//bruteCircle

71

Graphics Primitives

public void bresenhamCircle(int x0, int y0, int radius, Graphics g, int fudge){ int x =0; int y = radius; int di = 3 - 2*radius;

while(x < y/fudge) { eightPlot(x,y,x0,y0,g);

if (di <0){ di= di + 4 * x + 6; } else { di= di + 4 * (x-y) + 10; y--; } eightPlot(x,y,x0,y0,g); x++; } }//bresenhamCircle

public void incrementalCircle(int x0, int y0, int radius, Graphics g){

float x = float y =

radius; 0;

int P = 1; for(int i=1; radius>P; i++) {P *= 2;} float E = 1/(float)P;

while (y <= x){ x = x + (E*y); y = y - (E*x); System.out.println("(" + x + "," + y + ")"); eightPlot(Math.round(x),Math.round(y),x0,y0,g); } }//incrementalCircle

72

Graphics Primitives

public void paintComponent(Graphics g){

g.drawString("Brute force algorithm" ,50, 25 ); bruteCircle(100,90,50,g); bruteCircle(180,90,5,g); g.drawString("Incremental algorithm" ,50, 185 ); incrementalCircle(100,250,50,g); incrementalCircle(180,250,10,g);

g.drawString("Bresenham algorithm" ,50, 345 );

//

bresenhamCircle(100,410,50,g,6); bresenhamCircle(220,410,50,g,4); bresenhamCircle(340,410,50,g,2); bresenhamCircle(460,410,50,g,1); bresenhamCircle(60,120,5,g); }//paintComponent

}//end of -- Gcanvas ---------------------------------------------

Example 4. Implementation of the three circle drawing algorithms

Graphics Primitives

Figure 39. The output of the circle drawing program

73

74

Graphics Primitives

Chapter review - 4 The graphics primitive routines provided by any language are the foundation upon which any graphics program is based. Programmers rarely need to write there own primitives but the information in this chapter would allow that task to be performed. Graphics primitive routines need to be fast, as all graphics programs will make intensive use of them. For straight lines an easy to understand but inefficient approach and a more complex but efficient algorithm known as Bresenham’s algorithm have been presented. For circles, three algorithms of increasing efficiency have been presented.

4.4

Exercises - 4 4.1

Write a Java program that draws (say) 10,000 random straight lines using firstly the brute force approach and secondly Bresenham’s algorithm. Use Java’s clock facilities to time the execution of the two alternatives and demonstrate which is the more efficient. You may have to alter the number of lines drawn to obtain sensible answers depending on the speed of the computer you use.

4.2

Repeat the exercise for the three circle generating algorithms.

Data Structures and Drawing

75

Chapter 5 Data Structures and Drawing

5.1

Introduction This course in computer graphics is centred on understanding the algorithms used to generate 'on screen' graphics images. The intention is that you will understand how to program the generation of 2D and 3D images and how to manipulate those images through scaling, translation, rotation and projection. You will also learn how to colour and shade the images, remove hidden surfaces and create realistic lighting effects. Before any of that however, we must learn more about how to describe a drawing in terms that a computer can understand.

5.2

The basic 2d data structure Lets start at the bottom and work our way up. The first class we will tackle is Point2d.

76

Data Structures and Drawing

5.2.1

Point2d class The most basic thing we need to represent in graphics is the point. A point is basically two coordinates, x and y, which are “real” numbers. This suggests that we might define a Java class to represent a point that looks something like this (Figure 40): Point2d:

Point2d: myPoint

float: x float: y

x: -10 y: 10

Figure 40. The Point2d class and an instance of it. We shall refer to this class as Point2d to differentiate it from its 3d counterpart which we shall meet later on. The main task that objects of the Point2d class must accomplish is to store the x and y coordinates of the point. Thus in Java, the Point2d Class might look something like Example 5:

Example 5. The Point2d class

1 2 3 4 5 6

class Point2d{ public float x = 0; public float y = 0; }//end class Point2d

77

Data Structures and Drawing

5.2.2

Line2d Class Let us start considering lines by taking a look at a very simple drawing of a square. y

L1

N1

N2

(-10,10)

(10,10) L4

x

(0,0) L2 L3 N4 (-10,-10)

N3 (10,-10)

Figure 41. A square The square has the corners emphasised with blobs since we are going to refer to the corners as nodes and they have special significance for the data structure. Notice in the drawing that a line is defined between two nodes (for example line L1 connects nodes N1 and N2) but not all nodes are connected by lines (for example N1 is not connected to N3). We have seen in the Apollo 13 example, that lines are the basic constituents of drawing, and that each line has two end points. This suggests that an appropriate way to describe the drawings would be in terms of two classes: Line2d and Point2d.

78

Data Structures and Drawing

Thus to represent L1 in this scheme, we need 3 objects: see Figure 42 Point2d: N1 x: -10 y: 10 Line2d: L1 src: dest: Point2d: N2 x: 10 y: 10

Figure 42. Description of line L1 using objects In Java we can define the Line2d class like this:

Example 6. Line2d

1 2 3 4 5 6

class Line2d { private Point2d src; private Point2d dest; }//end class Line2d

79

Data Structures and Drawing

Thus an instance diagram of the objects required to describe our whole square might look like Figure 43. Line2d: L1 Point2d: N1

src: dest:

x: -10 y: 10

Point2d: N2 x: 10 y: 10 Point2d: N2a

Point2d: N1a

x: 10 y: 10

x: -10 y: 10

Line2d: L4

Line2d: L2

src: dest:

src: dest:

Point2d: N4a

Point2d: N3a

x: -10 y: -10

x: 10 y: -10 Point2d: N4 Point2d: N3

x: -10 y: -10 Line2d: L3

x: 10 y: -10

src: dest:

Figure 43. Data structure for a square You might at this point say, hang on, how come there are 4 nodes on this square, but you have 8 Point2d objects - surely your duplicating data unnecessarily. Well, yes and no. You could do it like Figure 44 and save your-

80

Data Structures and Drawing

self 4 objects, but that means that those lines share instances of the points and CAN NEVER BE CHANGED INDEPENDANTLY. From a data modelling point of view, the corners of the square are in fact two points that JUST HAPPEN to be in the same place. Point2d: N1

Line2d: L1

x: -10 y: 10

src: dest:

Point2d: N2 x: 10 y: 10

Line2d: L4

Line2d: L2

src: dest:

src: dest:

Point2d: N4

Line2d: L3

Point2d: N3

x: -10 y: -10

src: dest:

x: 10 y: -10

Figure 44. Alternative but problematic way of organising structure It is conceivable that during the execution of the program, you might want to change those lines into a completely different shape. With the original, resource hungry version, you have that flexibility available.

81

Data Structures and Drawing

5.2.3

Shape2d class We can generalise this a bit by saying that any shape can be described as a collection of lines. We, therefore introduce another class, Shape2d, which maintains a list of all of the lines that make up a shape. This could be implemented in many ways, in fact we shall use a Vector which we will call lines.

Shape2d: mySquare lines:

Line2d: L1

Point2d

src: dest:

x: y:

Point2d Line2d: L2 Line2d: L3 Line2d: L4 src: dest:

x: y:

src: dest:

src: dest:

Figure 45. Basic 2d data structure In addition, it is useful to know how many lines make up our shape. We use and integer named numberOfLines to keep count. Again, in Java, this would look something like this:

Example 7. Shape2d in Java

1 2 3 4

class Shape2d{ private Vector lines = new Vector(); private int numberOfLines=0;

82

Data Structures and Drawing

5 6

}//end class Shape2d

5.2.4

Drawing2d class A drawing can (obviously?) consist of many shapes - Example 46. We can therefore add a final class to our data model: Drawing2d, which can maintain another list (Vector) of the various shapes. y

L1

N1

N2

(-10,10)

N5

N6

(10,10) N7

L4

x

(0,0) L2 L3 N4 (-10,-10)

Figure 46. 2nd example diagram

N3 (10,-10)

83

Data Structures and Drawing

Drawing2d: myDrawing Shape2d: mySquare

shapes:

lines:

Shape2d: myArrow lines:

Figure 47. Basic 2d data structure

Example 8. Drawing2d in Java

1 2 3 4 5 6

class Drawing2d { private Vector shapes = new Vector(); int numberOfShapes=0; }//Drawing2d

So, in full, we have a drawing object, which has a list of shape objects, which have lists of line objects, which have two point objects each. This can be shown as an instance diagram - Figure 48, or slightly more formally as a class diagram - Figure 49

84

Data Structures and Drawing

Drawing2D

mySquare

Point2d

Shape2d

myArrow

Shape2d

Line2d

Line2d

Line2d

Point2d

Point2d

Point2d

Line2d

Line2d

Line2d

Point2d

Point2d

Figure 48. Summary instance diagram of the basic 2d data structure

Drawing2D

Shape2d

1 shapes

M

Line2d

1 lines

M

1 src 1 dest

Figure 49. Class diagram of the basic 2D data structure

1 1

Point2d

Data Structures and Drawing

85

Now all we have to do is work out what methods we need to do the drawing, and fit this into our structure of a simple graphics application and work out how the whole lot gets constructed in the first place. Simple really.

5.3

Adding methods At the moment, our classes are capable of storing data about graphics, but they are missing two things: •a means of actually assembling a structure from the classes. We

need to add constructors and accessor methods •any drawing functionality we need to add the actual commands

which cause things to be displayed on the screen. 5.3.1

Point2d class The main task that objects of the Point2d class must accomplish is to store the x and y coordinates of the point. To turn our existing version into something useful, we need a constructor to allow us to actually make objects and two methods which allow us access to the data inside the class:

Example 9. a working Point2d class

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

class Point2d{ public float x = 0; public float y = 0; private float k = 1; public Point2d(float in_x, float in_y) { x = in_x; y = in_y; }// end constructor public float x() { return (x); } public float y() { return (y); } }//end class Point2d

If we now wanted to use the Point2d class in a program, we could write something like:

Data Structures and Drawing

86

Point2d myPoint = new Point2d(100,150);

The newly created point object will now faithfully remember our coordinates for us. We can even ask it to tell us what it was we asked it to remember System.out.println (“(“ + myPoint.x() + “,” + myPoint.x() + “)”);

5.3.2

Line2d Class Line2d needs a constructor and a means of actually drawing lines:

Example 10. a working Line2d class

1 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

import java.awt.*;

class Line2d {

private Point2d src; private Point2d dest;

public Line2d (float x1, float y1, float x2, float y2) { src = new Point2d(x1,y1); dest = new Point2d(x2,y2); }//end constructor

public void draw(Graphics g){ g.drawLine((int)src.x(), (int)src.y(), (int)dest.x(), (int)dest.y()); }//end draw

}//end class Line2d

The constructor takes two sets or coordinates and uses them to set up its two points. The draw method is responsible for actually putting something on screen. It uses the drawLine() method of the Graphics class that we are familiar with from the Apollo13 example. Since we are using the Graphics class, we must import java.awt.* (see line 1) to make that class available to us.

Data Structures and Drawing

5.3.3

87

Shape2d class A means is needed of adding lines to shapes. To this end we can add a method called addLine() to our Shape2d class. It’s job is to add a line to the list of lines that make up a particular shape. In fact we can add two versions of this method; the first takes a ready made Line2d object as a parameter and just adds it to our list, the second takes in the coordinates of a line, constructs it for us and then adds it to the list. We need to keep track of how many lines make up each shape, so when ever a line is added to the lines Vector, we add 1 to the variable called numberOfLines. To actually draw a Shape2d on the screen, all we need to do is to tell each line that make up the shape to draw itself. That is exactly what method draw does. It uses a for loop to go through all of the lines and call each ones draw method. Effectively, Shape2d.draw() delegates the job of drawing to each line. See line 26 - which looks quite horrible, but is in fact, just us telling each line to draw itself.

Example 11. a working Shape2d class

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

import java.util.*; import java.awt.*; class Shape2d{ private Point2d localOrigin = new Point2d(0,0); private Vector lines = new Vector(); private int numberOfLines=0;

public void addLine(Line2d inLine){ lines.add(inLine); numberOfLines = numberOfLines + 1; }//end addLine

public void addLine(float x1, float y1, float x2, float y2){ Line2d myLine = new Line2d(x1, y1, x2, y2); lines.add(myLine); numberOfLines = numberOfLines + 1; }//end addLine

public void draw(Graphics g){ for (int i=0; i < numberOfLines; i = i+1) { ((Line2d)lines.get(i)).draw(g); }//end for i

}//end draw

Data Structures and Drawing

32 33

88

}//end class Shape2d

Lines 1 and 2 simply make sure that the Vector and Graphics classes are available to us. 5.3.4

Drawing2d class The Drawing2d class is very similar to the Shape2d class. In the same way as we need to be able to add lines to shapes, so we need to add shapes to drawings, hence the function addShape() (lines 10-13) Compare this to addLine in the previous example. Similarly, in order to draw a complete drawing, we just need to pass the drawing request on to all constituent shapes: (c.f. shapes -> lines)

Example 12. a working Drawing2d class

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

import java.util.*; import java.awt.*; class Drawing2d { private Vector shapes = new Vector(); int numberOfShapes=0;

public void addShape(Shape2d inShape){ shapes.add(inShape); numberOfShapes = numberOfShapes + 1; }//end addShape

public void draw(Graphics g){ for (int i=0; i < numberOfShapes; i = i+1) { ((Shape2d)shapes.get(i)).draw(g); }//end for i }//end draw

}//Drawing2d

Lines 1 and 2 simply make sure that the Vector and Graphics classes are available to us.

Data Structures and Drawing

5.3.5

89

Adding these classes to the simple drawing application The classes that we have just created are “real”, we can use them in a working program. By themselves, they don’t actually form a complete program (no main() function anywhere!). To make something that actually runs and draws on screen, we need to merge these classes into the simple graphics application that we have previously seen as the Apollo13 example. First of all, we modify our Gapp. This version of Gapp is EXACTLY THE SAME as the one we used for Apollo13 APART FROM lines 6-10 and the constructor.

Example 13. A working Gapp

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

import java.awt.*; import java.awt.event.*; import javax.swing.*;

public class Gapp extends JFrame { private Shape2d myShape = new Shape2d(); private Drawing2d myDrawing = new Drawing2d(); private Gcanvas myGcanvas = new Gcanvas();

public Gapp(){ setBackground(Color.white); myDrawing.addShape(myShape); myGcanvas.setDrawing(myDrawing); myShape.addLine((float)50, (float)50, (float)50, (float)150); myShape.addLine((float)50, (float)100, (float)100, (float)100); myShape.addLine((float)100, (float)100, (float)100, (float)150); myShape.addLine((float)150, (float)150, (float)150, (float)100); }// constructor

public void initComponents() throws Exception { setLocation(new java.awt.Point(0, 30)); setSize(new java.awt.Dimension(650, 400)); setTitle("Graphics Application"); getContentPane().add(myGcanvas); addWindowListener(new java.awt.event.WindowAdapter() { public void windowClosing(java.awt.event.WindowEvent e) { thisWindowClosing(e); } }); }//end - initComponents

Data Structures and Drawing

36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64

90

void thisWindowClosing(java.awt.event.WindowEvent e){ // Close the window when the close box is clicked setVisible(false); dispose(); System.exit(0); }//end - thisWindowClosing

static public void main(String[] args) { // Main entry point try { Gapp myGapp = new Gapp(); myGapp.initComponents(); myGapp.setVisible(true); } catch (Exception e) { e.printStackTrace(); } }//end main

}//end of class - Gapp1

Gcanvas has now acquired a variable called myDrawing which holds an instance of the Drawing2d class.

The paintComponent() function has been simplified, it simply asks myDrawing to draw itself (which in turn asks all of the shape objects, which ask the line objects).

Example 14. A working Gcanvas

1 2 3 4 5 6 7 8 9 10 11 12

import java.awt.*; import javax.swing.*; class Gcanvas extends JPanel { private Drawing2d myDrawing;

public void setDrawing(Drawing2d inDrawing) { myDrawing = inDrawing; }

91

Data Structures and Drawing

13 14 15 16 17 18 19

public void paintComponent(Graphics g){ myDrawing.draw(g); }//paintComponent }//Gcanvas

5.4

The completed system The final class diagram of the system should look something like this.(Figure 50) JDK - swing

JFrame

Drawing2D

JPanel

Shape2d

ours Gapp

Gcanvas

initComponents thisWindowclosing

paintComponent Line2d

Point2d

Figure 50. The design of a simple graphics application

Point2d

Data Structures and Drawing

5.5

92

The Dry Run Try dry-running this program by following the execution of the code. Draw the objects as they are created and added to the data structure. Draw the output by running through the paintComponent() method

Aside

Dry running is an invaluable technique for understanding, explaining and debugging code. It can be very involved and requires concentration, but is often the only way to REALLY understand what a piece of code does. If you’ve never dry run a piece of code, try this now. You’ll be a better programmer for it.

5.6

Further methods

5.6.1

Drawing vs erasing Suppose you want to change something on your drawing. What you need is an erase method in the Line2D class. It will need to be exactly the same as the draw method, but it will need to set the current drawing colour to the current background colour (usually white) before it draws its line. Shape2D will also need an erase method, which simply calls the erase method of all its constituent shapes. More details are given in section 8.2.1.

Data Structures and Drawing

93

Chapter review - 5 In this chapter you should have learned about: •How to represent a drawing in terms of point, line, shape and

drawing objects •How those classes can be implemented in Java •How to add methods to those classes which allow them to be

assembled into useful structures •How to add methods which cause the drawings to be displayed on screen.

Data Structures and Drawing

94

Exercises - 5 5.1

Get the example program from this chapter to work. Hint - copy the Gapp and Gcanvas files from the last exercise

5.2

Modify the constructor of the Gapp class to draw something more interesting e.g:

5.3

Define outstaring() methods for Point2d, Line2d, Shape2d and Drawing2d. Use them to produce a print out of the data used to construct your drawings. Your printout should look something like this: Drawing2d( Shape2d( Line2d( src: Point2d(100,50) dest: Point2d(50,200) )endLine2d Line2d( src: Point2d(300,300) dest: Point2d(200,200) )endLine2d )endShape2d Shape2d( Line2d( src: Point2d(22,43) dest: Point2d(82,43) )endLine2d )endShape2d )endDrawing

The rest of these section 5 exercises are optional as they aren’t strictly necessary to an understanding of graphics. However, if your Java skills are up to it, they are extremely useful techniques to master 5.4

Develop a method that writes your drawing data out to file (effectively a means of “saving” your drawing)

5.5

Develop methods that read the drawing data file, “load” the data back into a data structure and display it. This, in effect requires the construction of a simple parser.

95

2d Transformations

Chapter 6 2d Transformations

6.1

Introduction You may, with some justification, be wondering what was achieved by turning the simple Apollo13 program into the more complicated Basic2d application. Well, so far all of the work we have done has just been setting the scene - putting in place a Java framework that will allow us to deal with graphical information. Now we can actually start to work with that information, to manipulate it, to TRANSFORM it We shall look at transformation in two ways: •a simple way which introduces the concept and shows a simple

way of programming them. •a more complex, more powerful and more computational efficient

way of representing them

6.2 6.2.1

Transformations the simple way What is a transformation? Exactly what it says - an operation that transforms or changes a shape (line, drawing etc.) They are best understood graphically first. There are several basic ways you can change a shape: translation (moving it somewhere else), rotation (turning it round) and scaling (making it bigger or smaller). There are man

96

2d Transformations

y others, but we’ll worry about them later. 6.2.2

Translation Consider the arrow shown in Figure 51. We may want to move it from position A to position B. y

dy

B

A x

dx

Figure 51. Translation Essentially, we want to move the shape dx pixels along the x-axis and dy pixels up the y-axis. In fact all this means is moving each constituent point by dx and dy. To move a point in this manner, simply add the values of dx and dy to its existing coordinates. Example 15 shows what a translate() method of the Point2D class might look like.

Example 15. Point2d.translate()

1 2 3 4

Aside

public void translate(float dx, float dy){ x = x + dx; y = y + dy; }

It is worth pointing out the obvious; that if you want to move the shape left, then use a negative value for dx and likewise for down and dy!

97

2d Transformations

To translate a line, simply translate both of its end points:

Example 16. Line2d.translate()

1 2 3 4

public void translate(float dx, float dy){ src.translate(dx,dy); dest.translate(dx,dy); }//end translate

Note: No maths is involved anywhere other than in the Point2d class. Translating the “higher order objects” is simply a case a passing on the request to move to the constituent objects. The same applies to shapes and drawings:

Example 17. Shape2d.translate()

1 2 3 4 5

public void translate(float dx, float dy){ for (int i=0; i < numberOfLines; i = i+1) { ((Line2d)lines.get(i)).translate(dx,dy); }//end for i }//end translate

Example 18. Drawing2d.translate()

1 2 3 4 5

public void translate(float dx, float dy){ for (int i=0; i < numberOfShapes; i = i+1) { ((Shape2d)shapes.get(i)).translate(dx,dy); }//end for i }//end translate

98

2d Transformations

6.2.3

Rotation - (about the origin) Consider arrow B which we wish to rotate through an angle of 45° anticlockwise from A y

45°

B

A

x

Figure 52. Rotation The first thing to note is that any rotation must take place about a point which remains stationary. In this case, we are simply rotation the shape about the origin. This is, in fact, a special case (it makes the maths easier) and we shall see how to rotate a shape about some arbitrary point later on. To rotate any shape about the origin requires rotating each of its individual points. To work out how this is done, consider the coordinates of a point before and after the rotation: Both points will lie on the perimeter of a circle of radius r with its centre on the origin. See Figure 53:

99

2d Transformations

y

(x2,y2)

r (x1,y1)

θ

ψ x

Figure 53. Rotating a single point Time for some trigonometry: x2 = r cos (θ + ϕ) y2 = r sin (θ + ϕ) x2 = r (cos θ cos ϕ - sin θ sin ϕ) since x1 = r cos ϕ and y1 = r sin θ x2 = x1 cos θ - y1 sin θ

(Equation 28)

similarly: y2 = x1 sin θ + y1 cos θ

(Equation 29)

Stick this knowledge into a program, and you get this:

Example 19. rotation of a point in Java

1 1 2 3

public void rotate(float a) { float xtemp; xtemp = (x * (float)Math.cos(a)) - (y * (float)Math.sin(a)); y = (x * (float)Math.sin(a)) + (y * (float)Math.cos(a));

100

2d Transformations

4 5

x = xtemp; }

As with translation, applying the rotation transformation to the higher order objects is simply a matter of making sure that the request gets passed down the data structure to the appropriate points:

Example 20. Line2d.rotate(float a)

1 2 3 4

public void rotate(float angle){ src.rotate(angle); dest.rotate(angle); }//end rotate

Example 21. Shape2d.rotate(float a)

1 2 3 4 5

public void rotate(float angle){ for (int i=0; i < numberOfLines; i = i+1) { ((Line2d)lines.get(i)).rotate(angle); }//end for i }//end translate

Example 22. Drawing2d.rotate(float a)

1 2 3 4 5 6

Aside

public void rotate(float angle){ for (int i=0; i < numberOfShapes; i = i+1) { ((Shape2d)shapes.get(i)).rotate(angle); }//end for i }//end translate

Angles in Java are specified in radians not degrees 360° = 2 π rad angle_in_radians = angle_in_degrees * Math.PI / 180; or angle_in_radians = Math.toRadians(angle_in_degrees);

Aside

Rotation is measured in an anticlockwise direction. To turn a shape the other way, specify a negative value for the angle.

101

2d Transformations

6.2.4

Scaling Scaling a shape simply means making a it bigger or smaller. We can specify how much bigger or small be means of a “scale factor” - to double the size of an object we use a scale factor of 2, to half the size of an object we use a scale factor of 0.5. y

B

A x

Figure 54. Scaling Again, scaling of a shape is achieved by applying an operation to the individual points that make up the shape. In this case, the distance of a point from the origin changes by the scale factor.

y

x2 x1

A

B

y2

y1 x

Figure 55. Scaling an individual point.

102

2d Transformations

Simply multiplying the coordinates by the scale factor gives the new values of the coordinates x2 = x1 * sf

(Equation 30)

y2 = y1 * sf

(Equation 31)

Note that the scaling can be different in different directions: i.e. the x scale factor can be different to the y scale factor. By doing this we can stretch or squeeze a shape:

B

B xsf = 1

xsf = 4 ysf = 1

A

ysf = 4 (ish)

A

Figure 56. The independence of axes for scaling The implementation of this in Java gives us the following scale() method:

Example 23. Point2d.scale()

1 2 3 4

public void scale(float xScale, float yScale) { x = x * xScale; y = y * yScale; }

103

2d Transformations

The use of the method in the higher order classes follows the same pattern as translate() and rotate() and is therefore deliberately left for you to work out. 6.2.5

Identity transformations Some transformations lead to no change in the shape/line/point: •translate (0,0) •rotate (2 * Math.PI) •scale (1,1)

104

2d Transformations

6.2.6

Order of transformations The order in which transformations are applied to a shape is important. e.g. performing a translation followed by a rotation, will give an entirely different drawing to a performing the rotation followed by the same translation. - (Figure 57) y

x

rotation by 45° followed by translation (100,0)

y

x

translation (100,0) followed by rotation by 45°

Figure 57. Order of Transformations

105

2d Transformations

6.2.7

Rotation around local origin At the moment, our rotation is centred around the origin. What is often required is to spin the shape in-situ. - (Figure 58) y

local origin

x

Figure 58. Local rotation This can be achieved by combining two of our existing transformations: Translate the shape to the origin, rotate it the required amount about the origin and translate it back to where it was.

106

2d Transformations

) y

dx

translate(-dx,-dy)

dy

x

y

x

y

rotate(angle)

x

y

+dx

translate(dx,dy)

+dy x

Figure 59. Local rotation

107

2d Transformations

To do this, we need to know exactly which point the shape is to be rotated around. It is necessary, therefore to introduce the concept of a “local origin” to the Shape2d class.

Example 24. The local origin in class Shape2d

1 2 3 4 5 6 7 8 9 10 11 12 13

class Shape2d{ private Point2d localOrigin = new Point2d(0,0); private Vector lines = new Vector(); private int numberOfLines=0; public void addLine(Line2d inLine){ lines.add(inLine); . . . .

When the shape is transformed by any of the transformations we have seen so far, we must ensure that the local origin is subject to the same transformations. i.e. it must be moved along with all the lines. We can add a line of code (Example 25 - line 5) to the translate method. A similar line must be added to scale, rotate and any other transformations that may have been developed.

Example 25. Transforming the local origin

1 2 3 4 5 6

public void translate(float dx, float dy){ for (int i=0; i < numberOfLines; i = i+1) { ((Line2d)lines.get(i)).translate(dx,dy); }//end for i localOrigin.translate(dx,dy); }//end translate

When the shape is first created, in addition to specifying all of the constituent lines, the local origin (or “middle of the shape”) must also be given.

Example 26. Defining the local origin

1 2 3 4

apollo13 = new Shape2d(150,100); apollo13.addLine((float)50, (float)50, (float)50, (float)150); apollo13.addLine((float)50, (float)150, (float)100, (float)100); apollo13.addLine((float)100, (float)100, (float)100, (float)150);

108

2d Transformations

5 6 7 8 9 10 11 12

apollo13.addLine((float)100, (float)150, (float)200, (float)150); apollo13.addLine((float)200, (float)150, (float)200, (float)50); apollo13.addLine((float)200, (float)50, (float)100, (float)50); apollo13.addLine((float)100, (float)50, (float)100, (float)100); apollo13.addLine((float)100, (float)100, (float)50, (float)50); apollo13.addLine((float)200, (float)50, (float)250, (float)100); apollo13.addLine((float)250, (float)100, (float)200, (float)150); myDrawing.addShape(apollo13);

Aside

Local rotation is only meaningful at the shape level. The operation is not defined at line or point level. The actual code to perform the local rotation is given as Example 27

Example 27. Local rotation as implemented in Shape2d

1 2 3 4 5 6 7

public void localRotate(float angle) { float dx = localOrigin.x(); float dy = localOrigin.y(); translate(-dx,-dy); rotate(angle); translate(dx,dy); }//end localRotate

6.2.8

Other transformations - general cases/ special cases. From the section on local rotation it can be seen that both local rotation and rotation about the origin are, in fact, special cases of rotation about an arbitrary point. The general recipe for this is to translate the shape by an amount equal to the coordinates of the point of rotation but in a direction such that the point of rotation ends up at the origin. Perform the rotation, retranslate the shape by the same amount as before, but in the reverse direction. The formalisation of this algorithm and its implementation is left as an exercise for the reader. There are many other possible transformations including: reflection, reflection in an arbitrary line, shearing etc.

109

2d Transformations

Chapter review - 6 This chapter has •introduced the concepts of transformations:

- translation - scaling - rotating •covered how to: - Manipulate graphics on screen by transformations such as translation, rotation and scaling - Implement those transformations simply using methods •Discussed the difference between rotation about the origin and

local rotation •Discussed the importance of transformation order

110

2d Transformations

Exercises - 6 6.1

Modify the Basic2d program to include functions to translate, rotate and scale points, lines, shapes and drawings.

6.2

Use it to produce something like this:

111

Transformations as matrices

Chapter 7 Transformations as matrices

7.1

Introduction It is useful, for reasons that will become apparent later, to represent transformations using a matrix notation. Before we examine the useful tricks that this technique opens up to us, lets first see how it actually works.

7.2 7.2.1

The Transformations Rotation If we regard the point P as a vector from the origin defined by (x,y) then the two equations representing rotation (Equation 28 + Equation 29) can be written in matrix form:  x′   cos θ – sin θ   x    =     y′   sin θ cos θ   y 

If p and p’ represent the point P before and after rotation and R the rotation matrix, we can write the equation above more succinctly: p’ = R.p

(EQ 32)

112

Transformations as matrices

7.2.2

Scaling Similarly the scaling equations can be converted to: x′ = x sf 0 x y′ 0 y sf y

or p’ = S.p 7.2.3

(EQ 33)

Translation Once again, for translation: x′ = dx + x y′ dy y

or p’ = T + p 7.2.4

(EQ 34)

Homogenous coordinates One useful consequence of using matrices is that transformations that are combinations of transformations (remember that a local rotation is equivalent to a translation followed by a rotation followed by a translation) can be represented by multiplying the matrices together. Unfortunately, with the current set a matrices, this won’t work because Equation 32 & Equation 33 are multiplications, whilst Equation 34 is an addition. We can get round this by effectively including a “fiddle factor” and converting the matrices to use “homogenous coordinates”. What this actually means is that the addition necessary to perform translation can be achieved by multiplication. (We’ll prove this in a moment.) To convert our scheme of representation to homogenous coordinates we add an extra row to all point matrices and fill the extra position with a 1. x y 1 We then adapt our transformation matrices as below:

113

Transformations as matrices

7.2.5

Homogenous rotation x′ y′ = 1

7.2.6

cos ( θ ) – sin ( θ ) 0 x sin ( θ ) cos ( θ ) 0 y 0 0 1 1

Homogenous scaling x′ x sf 0 0 x y′ = 0 y sf 0 y 0 0 1 1 1

7.2.7

Homogenous translation x′ 1 0 dx x = y′ 0 1 dy y 1 0 0 1 1 Since everything is now done by multiplication, all the vector forms of these equations have the same form including translation: p’ = T.p

7.3

Implementing Matrices A matrix class can be implemented as follows:

Example 28. The Matrix class

1 2 3 4 5 6 7 8 9 10 11 12

class Matrix { public int rows = 0; public int columns = 0; public float [][] m;

Matrix(int in_rows, int in_columns, float val){ rows=in_rows; columns=in_columns; m = new float [rows][columns]; for (int i = 0; i < rows; i++ ) {

(EQ 35)

Transformations as matrices

13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67

114

for (int j = 0; j < columns; j++ ) { m[i][j] = val; }//end for j }//end for i }//constructor

public Matrix (float [][] in_data) { rows = in_data.length; columns = in_data[0].length; for (int i = 0; i < rows; i++) { if (in_data[i].length != columns) { throw new IllegalArgumentException("All rows must have the same length."); } } m = in_data; }//constructor

public int getRows(){ return (rows); }//

public int getColumns() { return (columns); }

public float getElement(int i, int j){ return(m[i][j]); }

public void setElement(int i, int j, float value){ m[i][j] = value; }

public Matrix mult(Matrix b){ float sum = 0; int i,j,k; if (rows != b.columns) { throw new IllegalArgumentException("Matrices are not conformable."); } Matrix result = new Matrix(b.rows,columns,0); for (i=0; i < b.rows; i++) { for (j=0; j < columns; j++){ sum = 0; for (k=0; k
Transformations as matrices

68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114

Aside

115

}//end for i }//end for j return result; }//end mult

public void transform(Matrix b) { float sum = 0; int i,j,k; if (rows != b.columns) { throw new IllegalArgumentException("Matrices are not conformable."); } float [][] result = new float[b.rows][columns]; for (i=0; i < b.rows; i++) { for (j=0; j < columns; j++){ sum = 0; for (k=0; k
public String toString(){ String result = new String("["); for (int i = 0; i < rows; i++ ) { result += "("; for (int j = 0; j < columns; j++ ) { result += m[i][j]; if (j != (columns-1)) { result += ", "; }//end if }//end for j result += ")\r"; }//end for i result += "]"; return(result); }// toString

}//end of class Matrix

The eagle-eyed reader will have spotted that two methods in the implementation above perform matrix multiplication, namely multiplication and transform. The difference between the two is that multiplication returns a new matrix object that holds the result of the multiplication whilst transform return nothing, but the state of the matrix to which the operation was applied

Transformations as matrices

116

is changed to reflect the result. Aside

As an alternative to this home made solution, a fully featured matrix mathematics package exists. Named JAMA it can be found at http:// math.nist.gov/javanumerics/jama/

7.4

Using the matrix class We need to change our implementation of points. Let Point2d inherit matrix:

Example 29. Point2d with matrices

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

class Point2d extends Matrix{ public Point2d(float in_x, float in_y) { super(3,1,1); m[0][0]=in_x; m[1][0]=in_y; }// end constructor

public float x() { return (m[0][0]); }

public float y() { return (m[1][0]); } }//end class Point2

We also need a new class - Transformation2d - which also inherits matrix. Start to think of transformations as things rather than operations applying a transformation object to a drawing, shape, line etc.

Example 30. Transformation2d

1 2 3 4 5 6

class Transformation2d extends Matrix{ Transformation2d(){ super(3,3,0); m[0][0]=1; m[1][1]=1;

Transformations as matrices

7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

117

m[2][2]=1; }//constructor

public void translate(float x, float y) { m[0][2]=x; m[1][2]=y; }//translate

public void rotate(float angle) { m[0][0] = (float)Math.cos(angle); m[1][0] = -(float)Math.sin(angle); m[0][1] = (float)Math.sin(angle); m[1][1] = (float)Math.cos(angle); }//rotate }//end class Transformation2d

We can remove all of the separate translate, rotate, scale etc. methods from Line2d, Shape2d and Drawing2d. They can be replaced with a single transform method:

Example 31. Line2d.transform()

1 2 3 4

public void transform(Transformation2d trans) { src.transform(trans); dest.transform(trans); }//end transform

Example 32. Shape2d.transform()

1 2 3 4 5 6

public void transform(Transformation2d trans) { for (int i=0; i < numberOfLines; i = i+1) { ((Line2d)lines.get(i)).transform(trans); }//end for i localOrigin.transform(trans); }//end transform

Transformations as matrices

7.4.1

118

Simple transformation using a matrix We can now use our new matrix machinery in an example simple transformation:

Example 33. Simple transformation with matrices

1 2 3 4 5 6 7 8 9 10

public Gapp(){ setBackground(Color.white); myDrawing.addShape(myShape); myGcanvas.setDrawing(myDrawing); myShape.addLine((float)50, (float)50, (float)50, (float)150); myShape.addLine((float)50, (float)100, (float)100, (float)100); myShape.addLine((float)100, (float)100, (float)100, (float)150); myShape.addLine((float)150, (float)150, (float)150, (float)100);

11 12 13 14 15 16 17 18 19

Transformation2d myTrans = new Transformation2d(); myTrans.rotate((float)(10*(3.14159262/180))); myTrans.translate(300,100); myDrawing.transform(myTrans); }// constructor

7.4.2

Combination of transformations using matrices When two matrices that represent different transformations are multiplied together, the resulting matrix represents the combined transformation. This leads to one of the main advantages of the matrix representation method: that a single matrix can be used to store the transformation that results from performing a sequence a transformations. i.e. we can concatenate transfomations. This has obvious efficiency implications: in the case of repetitively applying the a series of transformations, the computer will need to do fewer calculations if the concatention is available. A good example of this is the need to repeatedly spin an object about its centre (rotation about the local origin).

Example 34. rotation about local origin using matrices

1 2

float dx=apollo13.x(); float dy=apollo13.y();

119

Transformations as matrices

3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

Transformation2d Transformation2d Transformation2d Transformation2d

transform1 spin = new translate1 translate2

= new Transformation2d(); Transformation2d(); = new Transformation2d(); = new Transformation2d();

translate1.translate(-dx,-dy); spin.rotate((float)(-10 * Math.PI/180)); translate2.translate(dx,dy); transform1.transform(translate1); transform1.transform(spin); transform1.transform(translate2); myTrans=transform1;

Transformations as matrices

120

Chapter review - 7 This chapter has covered: •How to represent transformations as matrices and how to imple-

ment a matrix class in Java. •The use of homogenous coordinates to enable translation to be

performed by multiplication. •The concatenation of transformation by multiplication of matrices •How adapt our existing classes to use a matrix based representation technique.

Transformations as matrices

121

Exercises - 7 7.1

Develop and test a Java class to represent Matrices. As a minimum, the class should allow the multiplication of matrices.

7.2

Modify the Point2d class you have used in previous exercises to use a homogenous coordinate matrix representation.

7.3

Develop a Transformation class to represent transformations as matrices using the homogenous coordinate system.

7.4

Modify the basic 2d program to include functions to transform points, lines, shapes and drawings.

7.5

Derive and implement the homogenous matrices to represent the following transformations: •Translation •Rotation about the origin •Scaling •Rotation about the local origin •Reflection in the x axis •Reflection in y=x

Simple Animation and Interaction

122

Chapter 8 Simple Animation and Interaction

8.1

Introduction At the moment we can transform shapes etc., but don’t see it happen on screen; all the changes happen “off stage”, all we see are the results. It is useful to be able to see the changes happen under our control. Useful graphics programs can be written which don’t require user interaction - screen savers etc., but by allowing the user to interact in some way with the diagram, whole new vistas can be opened up. We need, therefore to master two techniques: acquiring user input (so we tell the program when and how to change our drawing) and animation to make the changes happen “on stage.” We shall discuss two types of animation/interaction - discrete and continuous. In discreet animation, the screen is redrawn (repainted) in response to some specific user action which usually also involves a single change to the data structure. With this kind of animation the drawing suddenly “jumps” from one state to another. It would be nice to see it gradually change - i.e. continuous animation. The key to such animation is the idea of showing a series of slightly different still images (or frames) in rapid succession. In our case, we might wish to achieve a rotation of 90° by repeatedly applying a rotation of 1° and displaying each interim image on the screen.

Simple Animation and Interaction

123

In continuous animation, the screen is constantly being refreshed (usually at a constant rate) to keep up with a continually changing data structure.

8.2

Changing the drawing in response to user interaction Let us begin with the simple case though - applying a simple transformation (lets use a translation by 50 units to the right) in response to some user input and displaying the transformed drawing. An example of this, is making this friendly frog jump for us when a key is pressed or a mouse is clicked.

Figure 60. Hopping frog - before and after hopping In order to achieve this, we must first master 3 simple tricks - erasing, getting user input and double buffering. 8.2.1

Drawing/erasing In order to show a new or transformed version of a drawing, it is necessary to first erase the old drawing and then rapidly draw the new one. The programs we have developed so far are equipped with draw() methods in the appropriate classes. These must now be supplemented with erase() methods. The Line2d.erase() method is identical to the Line2d.draw() method, except we set the drawing colour to white (the usual background colour) before drawing any lines. Indeed, we need to add a colour setting line to draw() to ensure that the drawing colour gets correctly set to black:

Example 35. Line2d.erase()

1 2 3

public void erase(Graphics g) { g.setColor(new Color(255,255,255)); g.drawLine((int)src.x(), (int)src.y(), (int)dest.x(), (int)dest.y());

Simple Animation and Interaction

4

124

}//end erase

Example 36. Line2d.draw()

1 2 3

public void draw(Graphics g){ g.setColor(new Color(0,0,255)); g.drawLine((int)src.x(), (int)src.y(), (int)dest.x(), (int)dest.y()); }//end draw

4

The usual mechanism of passing method calls from drawings to shapes to lines must also be implemented. 8.2.2

Getting keystrokes/mouse-events Getting user input in Java is a subject in itself. The whole architecture of and event objects is beyond the scope of this book. Instead, a simple example of getting mouse and keyboard events will suffice for most of what we need.

eventListeners

We can add our user input handling code to the constructor of the Gapp class:

Example 37. Getting/handling user input

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

addMouseListener ( new MouseAdapter() { public void mousePressed(MouseEvent evt) { System.out.println("Mouse!"); frog.transform(right); repaint(); } }); addKeyListener ( new KeyListener() { public void keyTyped(KeyEvent evt) { System.out.println("Key!"); frog.transform(right); repaint(); }//end keyTyped public void keyPressed(KeyEvent evt) { } public void keyReleased(KeyEvent evt) { } });

Simple Animation and Interaction

125

Now, when we run the program and either click the mouse or press a key, our frog will tell us what we just did and jump to the right. The syntax of all this is rather bizarre, we are using an “unnamed inner class” - check a good Java book for details. In addition, the keyPressed() method and keyReleased() methods don’t do anything. This is deliberate, but they have to be there, see the on-line Java documentation for why this is so. Aside

Such methods are generally known as event handlers 8.2.3

Double Buffering This technique is primarily used to achieve smoothness in continuous animation (rather than the discrete hop we are about to attempt). However, when implemented in the basic 2d framework that we are using, it has another useful property: that of allowing us to move all of the application specific code out of the Gcanvas class (which then become truly generic, in the sense that it can be used unaltered in any program, rather like, Line2d, Point2d etc.) and into the Gapp class which then becomes the sole repository for application specific details. Double buffering works by drawing all of the lines that make up a drawing into an off-screen buffer and then copying that buffer in a single operation into the computers video memory. This reduces on-screen “flicker” which can badly detract from the smoothness of an animation. In particular, we must add the following data members to our Gcanvas class:

Example 38. New data members for double buffering

1 2 3

Image bufferImage; Graphics bufferGraphics; Dimension d;

and the componentPaint() function must look like this.

Example 39. paintComponent() method using double buffering

1 2 3 4 5 6

public void paintComponent(Graphics g){ d = getSize(); bufferImage = this.createImage(d.width,d.height); bufferGraphics = bufferImage.getGraphics(); //draw to buffer

126

Simple Animation and Interaction

7 8 9 10 11 12 13 14 15 16 17

myDrawing.draw(bufferGraphics); //put buffer on screen g.drawImage(bufferImage,0,0,null); //capture current state to buffer bufferImage = createImage(d.width,d.height); bufferGraphics = bufferImage.getGraphics(); //apply erase to buffer myDrawing.erase(bufferGraphics); }//paintComponent

bufferGraphics

g

transform data structure draw to the buffer copy

perform erase transform data structure draw to the buffer copy

perform erase

Figure 61. Double buffering - two animation cycles 8.2.4

The hopping frog example The hopping frog example program that uses the three techniques outlined above is similar to many of our previous examples. It uses the basic 2d class structure and matrices to specify its one transformation. •The general recipe for our Gapp constructor is now: •Set up a drawing object,

Simple Animation and Interaction

127

•Set up a shape object •Add lines to the shape object •Add the event handlers

8.3

Continuous animation Continuous animation like movies, television and animated cartoons is a “trick”. All of these achieve the “impossible” feat of making a picture move by showing in very rapid succession a whole series of slightly different still images. A feature of the human visual system known as “persistence of vision” allows us to be fooled into seeing smooth, continuous action rather than a series of stills. The frame rate of an animation is the number of still displayed per unit time. Television uses a frame rate of 25 frames per second (fps), movie 24 fps whereas simple cartoon animation may use 12-15 fps. Continuous animation is characterised by the need for the screen to be refreshed to keep up with a constantly changing data structure. A common way of implementing this scenario is to use multiple threads with one thread handling the modifications to the data structure and another being responsible solely for repainting the screen. (i.e running the double buffering).

128

Simple Animation and Interaction

bufferGraphics

g

transform data structure draw to the buffer

thread A

copy

perform erase transform data structure draw to the buffer

thread B

copy

perform erase

Figure 62. Multithreaded Double buffering - two animation cycles A discussion of threading is again outwith the scope of this book. You are referred to any good Java textbook. The following example should serve to give you the basic idea. Both Gapp and Gcanvas extend the Runnable class. Each has a thread variable. The run() method of Gcanvas consists of an infinite loop which repaint()s the screen (causing the paintComponent() callback to be called) then pauses for 20ms.

Example 40. Multi-threaded Gcanvas

1 2 3 4 5 6

public void animateStart(){ thr.start(); }

Simple Animation and Interaction

7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

129

public void run() { try { for (;;){ //System.out.println("Gcanvas..."); repaint(); Thread.sleep(20); }//end for }// end try catch (InterruptedException e) { } }// end run

public void animateStop() { thr.stop(); }// end animate stop

The Gapp.run() method uses the same technique to repeatedly apply a local rotation transformation to the drawing every 20ms. Some mechanism to start the threads in response to a mouse click has also been added.

Example 41. Multi-threaded Gapp

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

public Gapp(){ . . . . . addMouseListener ( new MouseAdapter() { public void mousePressed(MouseEvent evt) { System.out.println("Mouse!"); myGcanvas.animateStart(); thr.start(); //repaint(); } }); }// constructor

public void run() { try { for (;;){ //System.out.println("Gapp..."); myDrawing.transform(myTrans); Thread.sleep(20);

Simple Animation and Interaction

28 29 30 31 32

130

}//end for }// end try catch (InterruptedException e) { } }// end run

It’s a bit difficult to show animation on the printed page (that’s why multimedia was invented!) but take it from me, this apollo13 is busy going round and round

Figure 63. Orbiting Apollo13

8.4

Animation changes in response to user interaction There is a third possible kind of animation whereby the data structure is constantly being changed, but the change itself changes in response to user interaction. This is the scenario commonly found in games. Use a switch to examine user input and set the transformation to be applied

Example 42. Defender - a simple game

33 34 35 36 37

addKeyListener (new KeyListener() { public void keyTyped(KeyEvent evt) { char myChar = evt.getKeyChar(); System.out.println("Key Typed!" + myChar); switch (myChar) {

Simple Animation and Interaction

38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53

case 'i' : myTrans=up; break; case 'j' : myTrans=left; break; case 'l' : myTrans=right; break; case ',' : myTrans=down; break; }//end switch repaint(); }//end keyTyped public void keyPressed(KeyEvent evt) { } public void keyReleased(KeyEvent evt) { } });

131

Simple Animation and Interaction

132

Chapter review - 8 Chapter 8 has covered the following topics •Discrete and continuous animation •Drawing and erasing •Getting user input from the mouse and keyboard in response to

events •event handling - altering transformations as a consequence •Double buffering •Multi-threaded double buffering

Simple Animation and Interaction

133

Exercises - 8 8.1

Implement the stearable spaceship game described in section 8.4.

134

Curves

Chapter 9 Curves

9.1

Introduction All of the programs developed so far represent the world entirely as straight lines. Whilst there are many occasions that this will suffice, a means of representing curves lines (and eventually curved surfaces) is a vital addition to the graphics programmers toolkit. We will look at two principal methods of curve representation Splines and Bezier curves. By looking at the mathematical foundations behind these techniques we will also be able to appreciate how other techniques such as Beta-Splines and NURBS work. The basic problem we are facing is to represent a curve easily and efficiently. We could take several easy “brute force and ignorance” approaches: •storing a curve as many small straight line segments

- doesn’t work well when scaled - inconvenient to have to specify so many points - need lots of points to make the curve look smooth •working out the equation that represents the curve - difficult for complex curves - moving an individual point requires re-calculation of the entire curve These are illustrated in Figure 64

135

Curves

Figure 64. Curves via brute force representation The more points/line segments that are used, the smoother the curve.

Figure 65. More segments = smoother curves What we want is to define a relatively small number of points and then use a technique called interpolation to invent the extra points for us.

136

Curves

9.2

Parametric Equations Before getting involved in curves, lets try to master interpolation and parametric equations using simple straight lines first. We already know that you can define a straight line in terms of its end points. P1

x1,y1 (13,8)

(7.5,4)

dy

P0 dx

x0,y0 (2,3)

Vdiff V1 V0

Figure 66. Parametric equations Imagine walking from P1 to P2 at a constant speed. Let us say that we start walking at P0 at time t=0 and we arrive a P1 at time t=1. Where are you at a general time t? dx = x1 - x0 dy = y1 - y0 x(t) = x0 + t.dx y(t) = y0 + t.dy In vector form:

137

Curves

vdiff = v1 - v0 v(t) = v0 + t. vdiff Let us just confirm that. Where are you at t=0.5? dx = 13 - 2 = 11 dy = 8 - 3 = 5 x(0.5) = 2 + (0.5 . 11) = 7.5 y(0.5) = 3 + (0.5 .5) = 5.5

Answer: You’ll be halfway there. We now have the equation of a straight line written as a function of some parameter t. (It is important to note that t doesn’t really signify time - that’s just the easiest way of explaining it.) Why do we use parametric equations? Suppose we stuck to the normal way of writing the equation of a straight line (y=mx+c) where y is expressed in terms of x. What happens when you draw a vertical line? Try working out the gradient - (m). It is, of course, infinity which is hard to handle on a computer. By using parametric representation, we can avoid such problems.

9.3

Splines One method of fitting a curve between a few points is know as the spline curve. This idea actually has a physical analogue and was used for many years in the shipyards to allow accurate curves to be marked out on a large scale on the steel plates which were cut and welded to make ships’ hulls. In

138

Curves

that case, a flexible metal strip (a spline) was bent to the approximate shape of the curve and heavy weights (knots) or pegs were used to force the strip to pass through particular (measured) points.

spline knots

Figure 67. Spline curve fitting 9.3.1

Representing Spline curves To represent a straight line above, we used a linear parametric equation (i.e. one where the highest power of t was 1). For curves, we need polynomial equations. Why? Because the graphs of polynomial equations wiggle.

Figure 68. A wiggly polynomial curve In fact, cubic equations (equations with t cubed - t3 -in them) are what we need. Quadratic (t2) lines aren’t wiggly enough, any higher powers are too wiggly and require more computation. The general form of a cubic parametric equation is: xt = a0 + a1t + a2t2 + a3t3 similarly for y

(Equation 36)

139

Curves

yt = b0 + b1t + b2t2 + b3t3

(Equation 37)

Ok, we know the general form, we know it wiggles, how do we make it wiggle where we want it to? Keep bearing in mind that what we want to do with these equations is use them to generate extra points on our curve that we will join with lots of short straight lines. Consider the following section of a curve:

Ni

Si+1

Ni+1

Si

Ni-1

Figure 69. Two segments of a spline Figure 69 shows two adjacent segments of an assumed smooth curve through a set of nodes N1 to Nm. Each segment is represented by cubic parametric equations like Equation 36 and Equation 37. i.e We know the coordinates of all N, we wish to deduce equations for each segment to allow us to generate several extra points per segment. For now, ignore the y equation, we’ll stick to x. For each segment we have an equation with four unknowns (a0-a3). For each segment, we need to find 4 boundary conditions which will let us find the unknowns. We can make use of the properties of the point where the curves meet (Ni). Property 1 - The lines meet At Ni, the last x value in segment Si (where t=1) is equal to the first x value in segment Si+1 (where t=0). Thus:

(Equation 38)

At Ni in segment Si (t=1) a’0 +a’1 + a’2 +a’3

At Ni in segment Si+1 (t=0) =

a0

140

Curves

where primed quantities (things with dashes - ‘) refer to segment Si and unprimed to segment Si+1 Property 2 - The join between the lines is continuous. The gradient of the end of segment Si is the same as the gradient at the start of segment Si+1 To find the gradient, we need to differentiate Equation 36 with respect to t: (Equation 39)

2  dx ------  dt  = a 1 + 2a 2 t + 3a 3 t

Again considering what happens in Si and Si+1 :

(Equation 40)

At Ni in segment Si (t=1)

 dx ------ = a 1' + 2a 2' + 3a 3'  dt  i

At Ni in segment Si+1 (t=0)

=

dx a 1 =  ------ dt i + 1

Property 3 - The lines meet smoothly - the 2nd derivative of Equation 36 is equal at the end of segment Si to that at the start of segment Si+1 First we differentiate Equation 39 to gain the 2nd derivative 2

d-------x= 2a 2 + 6at 2 dt

And once more considering what happens in Si and Si+1

141

Curves

:

(Equation 41)

At Ni in segment Si (t=1)

 d 2 x  -------2- = 2a 2' + 6a 3'  dt  i

At Ni in segment Si+1 (t=0)

=

 d 2 x 2a 2 =  -------2-  dt  i + 1

We need one more boundary condition. This can be obtained from the end points of the line as a whole. One of three endpoint conditions is usually specified: •Fixed gradient - give a value for dx/dt at either end of the curve. •Free end - The gradient is unconstrained - in effect the 2nd deriva-

tive is 0 •Contour - The ends of the line meet, making the last point and the first point identical. c.f a contour line on a map. The equations mentioned in properties 1-3 occur for every segment of the line. Taken together all of this lead to a set of simultaneous equations that can be solved to find the values of a0-a3 for each segment. The solution of simultaneous equations is outside the scope of this course. Look up matrix representation of simultaneous equations and Gaussian elimination in any good maths text book. The resulting expression can be found on line 26 of Example 43. Now all we need to do, is exactly the same for y and we are in a position where we have a means of calculating x and y for any value of t. 9.3.2

Implementing Splines It is convenient to represent splines using a spline class. The class will need a vector to store its point objects, a count of how many points it has and a means of adding points to the spline

9.3.2.1

Drawing the curve Actually drawing the curve requires that we split each segment into many sub-segments, calculate their endpoints and draw straight lines between them.

142

Curves

9.3.2.2

Transforming a spline A spline can be transformed by simply applying the required transformation to each of its constituent points. Remember the spline itself has few points to be transformed, its just a matter of re-interpolating between them after they have been transformed.

Example 43. The Spline2d class

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34

import java.awt.*; import java.util.*; class Spline2d { Vector points = new Vector(); int numberOfPoints =0; int segments = 10; float interval = (float)1/segments;

Spline2d(){ for (int i=0; i<10; i++) { System.out.println(fact(i)); } }

public void addPoint(float x, float y){ points.add(new Point2d(x,y)); numberOfPoints++; }//end addPoint

public float interpolate(float t, float a, float b, float c) { return((float) (2*(0.5-t)*(1-t)*a + 4*t*(1-t)*b + 2*t*(t0.5)*c)); }

public void draw(Graphics g){ float xdest = 0; float ydest=0; for (int section=0; section < numberOfPoints-2; section++){

35 36 37 38 39 40 41 42 43 44

float ax = ((Point2d)points.get(section)).x(); float ay = ((Point2d)points.get(section)).y(); float bx= ((Point2d)points.get(section+1)).x(); float by= ((Point2d)points.get(section+1)).y(); float cx= ((Point2d)points.get(section+2)).x(); float cy= ((Point2d)points.get(section+2)).y(); float xsrc=ax;

143

Curves

45 46 47 48 49 50

float ysrc=ay; for (float t=0; t<=1.001 ; t+= interval){ xdest=interpolate(t,ax,bx,cx); ydest=interpolate(t,ay,by,cy); g.drawLine((int)xsrc, (int)ysrc, (int)xdest, (int)ydest);

51 52 53 54 55 56 57 58 59 60 61 62 63 64

xsrc=xdest; ysrc=ydest; }//for t }//for section }//end draw

public void transform(Transformation2d trans) { for (int i=0; i < numberOfPoints; i = i+1) { ((Line2d)points.get(i)).transform(trans); }//end for i }//end transform }//end class Spline2d

9.3.3

Disadvantages of splines •Changing the position of a single node will require recalculation of

the entire curve •Overly restrictive to force the curve through each point.

9.4

Bezier Curves An alternative to splines are Bezier curves. M. Bezier was a French mathematician who worked for the Renault motor car company. He invented his curves to allow his firm’s computers to describe the shape of car bodies.

144

Curves

9.4.1

Representing Bezier curves The basic idea behind Bezier curves is that, unlike splines, not each and every point need actually be on the line. The points act as control points attracting the drawn line towards themselves. Once again a parametric representation is used the idea being that the “influence” or “attraction” on the curve of each point varies with t.

N1

N2

N3 N0

Figure 70. A Bezier curve In Figure 70, at t=0, N0 is the major (100%) influence on the line and the others have no effect. At t=1, the situation has changed to the state where N0 has no effect on the line, but N3 attracts it completely. N1 and N1 are never 100% in control as that would mean the line passing through them. Once again polynomials are used to create the curve and the influence that each point exerts upon the curve at point t is given by a polynomial equation (the Bezier polynomial). A 4-point curve will have 3 segments (n=3) as in Figure 71:

N1

N2

N3 N0

Figure 71. A curve of 3 segments

145

Curves

The Bezier polynomials for a 4 point curve are: B03

B13

influence

influence

t=0

t=0

t=1

B33

B23

influence

influence

t=0

t=1

t=0

t=1

t=1

Figure 72. The Bezier Polynomials The equation of these curves is n! i n–i B in ( t ) = --------------------- ⋅ t ⋅ ( 1 – t ) : i! ( n – i )! The parametric equation actually representing the curve can be regarded as the sum of the influences, i.e the sum of the Bezier polynomials: n

x(t) =

∑ xi ⋅ Bin ( t )

(Equation 42)

i=0 n

y(t) =

∑ yi ⋅ Bin ( t ) i=0

i.e. Bin(t) represents the influence of point xi,yi

(Equation 43)

146

Curves

9.4.2

Implementing Bezier curves The basics of the Bezier class are the same as the spline, a mechanism for storing/adding the points.

9.4.2.1

Solution based on Bezier polynomials In the drawing function, the bezier polynomials can be used directly. This however is not a particularly efficient implementation.

Example 44. Direct evaluation the Bezier polynomials

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40

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

class Bezier2d { Vector points = new Vector(); int numberOfPoints =0; int segments = 10; float interval = (float)1/segments;

Bezier2d(){ for (int i=0; i<10; i++) { System.out.println(fact(i)); } }

public void addPoint(float x, float y){ points.add(new Point2d(x,y)); numberOfPoints++; }//end addLine

int fact(int x){ if (x == 0) return } else return } }//fact

{ 1; { (x * fact(x-1));

int choice(int n, int i) { return(fact(n)/((fact(i)*fact(n-i))));

147

Curves

41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85

9.4.2.2

}//c

float bezier(int i, int n, float t){ float bint = choice(n,i) * (float)Math.pow(t,i) * (float)Math.pow((1-t),n-i); System.out.println("bezing: i = " + i + ", n = " + n + ", t= " + t + ", bint = " + bint); return( bint ); }//bezier

public void draw(Graphics g){ // set colour black g.setColor(new Color(0,0,0)); System.out.println("drawing"); float xsrc=((Point2d)points.get(0)).x(); float ysrc=((Point2d)points.get(0)).y(); for (float t=0; t<=1.1 ; t+= interval){ float xdest=0; float ydest=0; for (int i=0; i
public void transform(Transformation2d trans) { for (int i=0; i < numberOfPoints; i = i+1) { ((Line2d)points.get(i)).transform(trans); }//end for i }//end transform

}//end class Bezier2d

Recursive technique A more interesting technique is the recursive means of drawing ever closer approximations to a true Bezier curve Figure 73.

148

Curves

.

B f C

h e

j

i g

A D

Figure 73. Recursive method of drawing Bezier curves The algorithm can be informally described thus: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

Start with 4 known points (a,b,c,d) Calculate the midpoints (e,f,g) between those points: ex = ax + (bx-ax)/2 and similarly for all others Calculate the midpoints (h,i) Calculate the midpoint (j) if the length of the lines ae, eh, hj are all less than q (where q is choosen to give a smooth curve - typically 1 < q <10) then draw straight lines ae,eh,hj else start again using a,e,h,j as the four known starting points if the length of the lines ji, ig, gd are all less than q then draw straight lines ji, ig, gd else start again using j,i,g,d as the four known starting points

A Java implementation of this is given as Example 45.

Example 45. The recursive algorithm

1 2

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

149

Curves

3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53

class Bezier2d { Vector points = new Vector(); int numberOfPoints =0; int segments = 10; float interval = (float)1/segments;

Bezier2d(){ for (int i=0; i<10; i++) { System.out.println(fact(i)); } }

public void addPoint(float x, float y){ points.add(new Point2d(x,y)); numberOfPoints++; }//end addLine

public void recDraw2(Point2d a, Point2d b, Point2d c, Point2d d, Graphics gr){ Point2d e,f,g,h,i,j; float ae,eh,hj,ji,ig,gd; e f g h i j

= = = = = =

new new new new new new

Point2d(a.x() Point2d(b.x() Point2d(c.x() Point2d(e.x() Point2d(f.x() Point2d(h.x()

+ + + + + +

(b.x()-a.x())/2, (c.x()-b.x())/2, (d.x()-c.x())/2, (f.x()-e.x())/2, (g.x()-f.x())/2, (i.x()-h.x())/2,

ae = (float)Math.sqrt( Math.pow(e.y()-a.y(),2) ); eh = (float)Math.sqrt( Math.pow(h.y()-e.y(),2) ); hj = (float)Math.sqrt( Math.pow(j.y()-h.y(),2) ); ji = (float)Math.sqrt( Math.pow(i.y()-j.y(),2) ); ig = (float)Math.sqrt( Math.pow(g.y()-i.y(),2) ); gd = (float)Math.sqrt( Math.pow(d.y()-g.y(),2) );

a.y() b.y() c.y() e.y() f.y() h.y()

+ + + + + +

(b.y()-a.y())/2); (c.y()-b.y())/2); (d.y()-c.y())/2); (f.y()-e.y())/2); (g.y()-f.y())/2); (i.y()-h.y())/2);

Math.pow(e.x()-a.x(),2) + Math.pow(h.x()-e.x(),2) + Math.pow(j.x()-h.x(),2) + Math.pow(i.x()-j.x(),2) + Math.pow(g.x()-i.x(),2) + Math.pow(d.x()-g.x(),2) +

float l =10; if ( ae>l || eh>l || hj>l || ji>l || ig>l || gd>l){ recDraw2(a,e,h,j,gr); recDraw2(j,i,g,d,gr); } else { gr.drawLine((int)a.x(),(int)a.y(),(int)e.x(),(int)e.y()); gr.drawLine((int)e.x(),(int)e.y(),(int)h.x(),(int)h.y());

150

Curves

54 55 56 57 58 59 60 61 62 63 64

gr.drawLine((int)h.x(),(int)h.y(),(int)j.x(),(int)j.y()); gr.drawLine((int)j.x(),(int)j.y(),(int)i.x(),(int)i.y()); gr.drawLine((int)i.x(),(int)i.y(),(int)g.x(),(int)g.y()); gr.drawLine((int)g.x(),(int)g.y(),(int)d.x(),(int)d.y()); } }//recDraw

public void draw(Graphics g) { g.setColor(new Color(0,0,255)); recDraw2((Point2d)points.get(0),(Point2d)points.get(1),(Point2d)poi nts.get(2),(Point2d)points.get(3),g); }// recursive algorithm

65 66 67 68 69 70 71 72 73 74 75 76 77

public void transform(Transformation2d trans) { for (int i=0; i < numberOfPoints; i = i+1) { ((Line2d)points.get(i)).transform(trans); }//end for i }//end transform }//end class Bezier2d

Proof of the equivalence of these techniques is most definitely left to the reader!

9.5

Other Curves Several other techniques exist for representing curves. Among those which are beyond the scope of this book are: •Beta Splines - give reference •Nurbs (non-uniform, rational beta-splines)

9.6

The co-existence of multiple kinds of line In order to allow Line2d objects, Spline2d objects and Bezier2d objects to coexist in the same Shape2d, we can use the fact that Shape2d’s vector of lines can maintain of heterogeneous collection of objects - i.e. they can all be in the vector at once. In order to use them, however, we need to use polymorphism to allow us to call a draw method without knowing the type of the object who’s draw method we are calling. By introducing a Java Interface called GItem2d and having it implemented by Line2d, Spline2d and Bezier2d we can achieve the desired effect.

151

Curves

An illustration of this can be seen in the online example optimised2d example on the associated website : http://www.cs.strath.ac.uk/~if/classes/52.359

GItem2d

Matrix

M

Shape2d

1

Line2d

Bezier2d

Point2d 1

1

1

1

JPanel

JFrame

Drawing2D

1 Gcanvas

Gapp

paintComponent

1

initComponents thisWindowclosing

1

Transformation2d

M

1

Figure 74. A class structure for 2d graphics

1

152

Curves

Chapter review - 9 In this chapter you have seen: •The need to generate curves through interpolation •The use of parametric equations to represent curves •The derivation of Spline curves •A simple implementation of Spline curves •The derivation of the Bezier curves •Two methods of drawing Bezier curves

- One based upon direct evaluation of Bezier polynomials - One based upon a recursive technique

153

Curves

Exercises - 9 9.1

Create and test a Java program to implement both means of plotting Bezier curves.

9.2

Compare the curves produced by both means to ensure that they produce identical results

9.3

Compare the relative efficiency of the two methods of generating the curves in terms of the number of mathematical operations that have to be performed to generate identical curves.

154

3D graphics

Chapter 10 3D graphics

10.1

Introduction In the previous chapters we have mastered most of the techniques required for 2d graphics. We could now write applications as diverse as mapping systems, video games and CASE tools. We are now ready to take a surprisingly small quantum leap into 3-dimensional graphics. To me, this is the real topic of the book. 2d applications are fine and in many cases exactly what is required, but there is something almost magical about staring “through” a computer screen into a cyberspace and seeing worlds and objects that don’t really exist. We will consider two topics here: Firstly, how to represent 3d space in a data structure (c.f. the basic 2d data structure) and secondly how that representation can be displayed on our flat screens. Both of these tasks are best described in terms of how the 2d systems can be expanded into the 3rd (depth) dimension.

10.2

The 3D coordinate system In two dimensions we considered only two coordinates x and y (or breadth and height if you will). To add depth to a geometrical description we need only add a third coordinate (z) which will signify depth. By convention we use 3 mutually perpendicular axes, but from a mathematical point of view, any 3 non co-planar axes would do. Any point in 3d space can thus be represented by just those three numbers x,y & z.

155

3D graphics

y (0,5,0)

(5,5,0)

c g

f

d

b

k (5,5,5)

(0,5,5)

(5,0,0)

a (0,0,0) l

x

j

h e

(0,0,5)

i

(5,0,5)

z

Figure 75. 3d coordinate space Figure 75 shows a representation of a cube using our new coordinate system. Each line of the cube (a-l) can still be described by its endpoints just as in 2d, but each point now has 3 coordinates. In terms of objects required to describe the scene, the good news is that we can still use our drawing, shape, line and point classes. The bad news is that they are all going to require some (hopefully small) alterations to work in 3d.

10.3 10.3.1

Implementing 3d - the basic 3d class structure Points Each point must have an additional z coordinate, but since we shall continue the practise of using homogenous coordinates, each point now requires 4 numbers for complete representation. For now, we shall stick to using the shape and drawing classes though this will change we consider a more powerful technique in Chapter 11 “Improving visual realism”

156

3D graphics

x y z 1

10.3.2

3d Transformations Since we now have an additional coordinate to cope with, out transformation matrices must also expand to cope. In fact, each transformation matrix with require an extra row and an extra column to become 4x4 matrices.

10.3.2.1

Scaling The reason for this becomes self evident if we think about the scaling transformation:

Sx 0 0 0 x' y' = 0 S y 0 0 z' 0 0 Sz 0 1 0 0 0 1

x y z 1

Note that the vector equation doesn’t change from the 2d case: p’ = S.p

(EQ 44)

157

3D graphics

y

x

z

Figure 76. 3d Scaling

10.3.2.2

Translation The maths behind the translation are again sufficiently similar to the 2d case to warrant no further explanation

1 0 x' y' = 0 1 z' 0 0 1 0 0

0 Tx x 0 Ty y 1 Tz z 0 1 1

158

3D graphics

y

x

z

Figure 77. 3d Translation

10.3.2.3

Rotation In 2 dimensions, rotation were defined to take place about some stationary point - the centre of rotation. In 3d, all rotation take place about one of the axes. y

y

y

θ +ve

θ +ve x z

x z

Figure 78. axes of rotation

z

θ +ve

x

159

3D graphics

y

θ =-90°

x z

Figure 79. Example rotation -90° around the x-axis This means that 3 matrices are required, one to represent rotation about each axis +ve rotation about x

x' 1 0 0 0 y' = 0 cos θ sin θ 0 z' 0 – sin θ cos θ 0 1 0 0 0 1

x y z 1

+ve rotation about y

x' cos θ y' = 0 z' – sin θ 1 0

0 sin θ 0 1 0 0 0 cos θ 0 0 0 1

x y z 1

160

3D graphics

+ve rotation about z

x' cos θ sin θ 0 0 y' = – sin θ cos θ 0 0 z' 0 0 10 1 0 0 01

x y z 1

The relationship between these matrices and that introduced in 2d rotation is again hopefully obvious - indeed the multiplication coefficients are identical and the different “patterns” in the matrix are of course just to ensure that the right coordinates are multiplied. These matrices can be multiplied to produce matrices that represents combinations of rotations.

Figure 80. Rotation of a cube about x,y and z axes

161

3D graphics

10.3.2.4

Local rotation Once more this is similar to the 2d situation. The basic rotations are defined about the axes. It is therefore necessary to translate the object to be rotated to the origin, perform the rotation and translate it back to where it started from.

y

θ =-90°

x z

Figure 81. Example rotation -90° around the x-axis

10.4

Projections - Viewing 3d on a flat screen At the time of writing, the majority of computer display systems have 2dimensional display devices (the screen). 3d holographic devices do exist, but are (as yet!) confined to research labs and specialist applications. So, for now, we have to have a way of displaying our computerised models of 3d objects on flat screens. Fortunately the technique of projection was developed many years ago by architects and draughtsmen.

162

3D graphics

10.4.1

Projection - some definitions Projection is based on the idea that the screen placed between us and the object which exists in some space behind the screen. To draw onto that screen, we project lines from the eye of the observer (the centre of projection) to the object we wish to represent. Where those lines intersect the screen, we draw. view volume y

x extent z x

A A’

C

B’

B

centre of projection

screen

y extent

z extent

Figure 82. Projection In Figure 82, A’B’ is the projection of the line AB in the view volume. If C is a finite distance from the screen, then a perspective projection results. This used when visual realism is the goal of the system. By allowing C to be infinite a parallel projection is obtained - this is much used in engineering drawings, where parallel line in the view volume always produce parallel lines on the screen. Note that for this to work properly, the use has to position they head in the right place relative to the screen. In practise, the numbers involved can be chosen to make quality of the view relatively insensitive to minor movements from the normal viewing position ok. Aside

As described above, there is only one centre of projection. Humans however have two eyes. By producing two simultaneous projections based on two slightly different centres of projection (about 8cm apart) and feeding them separately to the left and right eyes, a passable imitation of 3d can be obtained. This is the basis of VR headset “goggles”. Several different projections exist, each with its own properties and uses. We shall examine some of the common ones. Since the projection on screen of any point in the view volume can be calculated, it is convenient to repre-

163

3D graphics

sent that calculation as a transformation and hence as a matrix For each style of projection the matrix is given along with an a representation of a cube in that projection. 10.4.2

Parallel projection. A parallel projection is one where all the lines that are parallel in 3d space remain parallel when projected. For any point (x,y,z) the parallel projection is to screen coordinated (xp,yp). Simply use xp=x and yp=y and quietly ignore the z coordinate. In matrix form this is: 1 0 0 0

0 1 0 0

0 0 0 0

0 0 0 1

x xp y = yp 0 z 1 1

Figure 83. example parallel projection - cube house

164

3D graphics

10.4.3

Perspective projection The simplest perspective projection is the orthogonal projection. In simplest terms, this the projection that results when looking straight down the yaxis. d C

z p’ (xplot,yplot)

By similar triangles: x xp ---------= -----z+d d

p (x,y,z) screen

x xp = -----------z 1 + --d

x

y yp = -----------z 1 + --d

Figure 84. Derivation of the perspective project transformation

10 0 0 01 0 0 00 0 0 1 0 0 --- 1 d

x x y y = 0 z z 1 + --1 d

we actually want: x ----------z 1 + --d y - ∴ ÷  1 + --z- ---------- d z 1 + --d 0 1

165

3D graphics

Figure 85. Example perspective projection 10.4.4

Oblique Parallel projections A parallel projection is one where all the lines that are parallel in 3d space remain parallel when projected. An oblique parallel projection is one where the object in question is viewed “from the side” - unlike the plain parallel projection. The z axis is drawn at some angle (α) to the x axis and z coordinates are multiplied by some factor λ. Consider the point P in Figure 86. z y

λ α

(0,0,1) P (xp,yp)

x

Figure 86. The derivation of oblique parallel projection transformations •P can be represented in 3D space - (0,0,1) •P can be represented in 2D (on screen coordinates) - (xp,yp) •P can be represented in 2D via polar coordinates - (λ,α)

xp= λ cos α

166

3D graphics

yp= λ sin α This is valid only for (0,0,1). For a general point (x,y,z) we need to multiply by z and allow for x and y which represent the offset in 2d terms. Thus generally: xp = x + zλ cos α yp = y + zλ sin α In matrix form:

1 0 0 0

0 λ cos α 1 λ sin α 0 0 0 0

0 0 0 1

x xp y = yp 0 z 1 1

A number of standard oblique parallel projections are used in engineering drawing. They can all be specified in terms of (α,β) or (α,λ) since clearly (!) tan β = 1/λ y

β

λ α

x

Figure 87. tan Β = 1/λ

(0,0,1) z

167

3D graphics

λ =1

(β=45°)

cavalier projection

λ= 0.5

(β=63.4°)

cabinet projection

λ=0

(β=90°)

orthogonal projection

a = 0 - 360°

Table 4. Principal Engineering Projections

10.4.4.1

Cavalier. In the cavalier projection the length of a line on the screen is equal to its length in the model. This causes a distortion by over emphasising the z-axis.

Figure 88. example cavalier projection

10.4.4.2

Cabinet. The foreshortening of the z axis is increased to provide a more “realistic” view.

168

3D graphics

Figure 89. example cabinet projection

10.4.4.3

Orthogonal The orthogonal projection (where λ=0) is just the simple parallel projection we met in 10.4.2

169

3D graphics

10.4.5

Isometric Projection The isometric projection has the property that all 3 axes are equally foreshortened allowing measurements along the axes to made with the same scale.

Figure 90. Example isometric projection The matrix representation of the isometric projection is: sin 60° – cos 60° 0 0

0 – sin 60° 1 – cos 60° 0 0 0 0

0 0 0 1

170

3D graphics

10.5

Viewpoint Transformation In addition to the perspective transformation, there is one other transformation that we may wish to apply to the data before it is displayed on the screen. The viewpoint transformation gives the ability to move, pan, tilt and zoom the “camera” through which we view our worlds. Figure 91 shows the same scene being viewed from two different angles. y z

x

y z

x

Figure 91. Looking at the world from a different angle Moving the viewpoint by viewpoint transformation allows us to describe the position of the camera in terms a translation of its position and a rotation of its orientation. Figure 92 and Figure 93 show how the viewpoint can be

171

3D graphics

represented in terms of familiar transformations. A (camera or viewpoint) is described as having “six degrees of freedom” i.e. we can translate it in the x, y and z directions and we can spin it about the x,y, and z axes.

y z dy

dz x dx

Figure 92. Moving the camera = translating the viewpoint

172

3D graphics

Figure 93. Rotating the camera about its x,y and z axes Fortunately we already have all the mathematical tools required to represent the position and the orientation of the viewpoint at our disposal in the form of the matrices from the previous section. In practice, we don’t actually move the camera, what we do is the transform the world in precisely the opposite sense to the way we would have moved the camera. Figure 94 attempts to show that these operations are in fact equivalent and that you end up with the same view. The reason that we do it that way is simply that it is easier to program that way. Think of it in terms of an idle cameraman who has the world moved instead of shifting the camera

173

3D graphics

y z moving the camera x

same view

y

zy x

z x

moving the world

Figure 94. Moving the camera or moving the world Viewpoint transformation is itself a combination of 2 separate transformations, one for the rotation, one for the translation. These two transformations must be concatenated to produce the complete transformation. It is important to note that the rotation represents a local rotation of the entire world about the position of the camera and must be applied first.

10.6

Implementing 3d - the data model We shall tackle the implementation of all of this by first changing our existing 2d classes to implement a basic model of the 3d world. For now, we aren’t going to change the basic class structure at all, no new classes, no new relationships between the classes.

174

3D graphics

The basic principle here is that all the classes except Gapp are generic, in the sense that they could be used in any application that needs to manipulate graphics. What goes into Gapp is dependant upon the application under constrution. Gapp would look very different for a CAD tool when compared with a game. For now, we shall continue using a Gapp that has some simple key based controls that allow us to perform simple transformations on the data model. Figure 95 serves as a reminder of the structure under discussion) GItem3d

Matrix

M

Shape3d

1

Line3d

Bezier3d

Point3d 1

1

Transformation3d

M

1

1

JPanel

1

JFrame

Drawing3D

1 Gcanvas

Gapp

paintComponent

1

initComponents thisWindowclosing

1

1

Figure 95. A basic 3d data structure We can now consider the necessary changes from 2d to 3d one class at a time:

175

3D graphics

10.6.1

The Matrix Class Fortunately we did all the hard work here in 2d. This class doesn’t change at all.

10.6.2

Point3d Point3d doesn’t require too much work. The changes can be summarised thus: •Add an extra (z) coordinate •change constructor, toString and accessor methods to match.

Example 46. Point3d

1 2 3 4 5 6 7 8 9 10 11

class Point3d extends Matrix { public Point3d(float in_x, float in_y, float in_z) { super(4,1,1); m[0][0]=in_x; m[1][0]=in_y; m[2][0]=in_z; }// end constructor public String toString(){ return ("("+m[0][0] + "," + m[1][0] + "," + m[2][0] + ")");

12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

}//toString public float x() { return (m[0][0]); } public float y() { return (m[1][0]); } public float z() { return (m[2][0]); } public void setx(float x){ m[0][0] = x; } public void sety(float y){ m[1][0] = y; } public void setz(float z){ m[2][0] = z;

176

3D graphics

36 37 38

10.6.3

} }//end class Point3d

Gitem3d Since each GItem3 needs to know how the viewpoint has been transformed, we can define a static data member (i.e one that is shared by all instances of the class) to hold that transformation.- the drawing transformation

Example 47. GItem3d

1 2 3 4

import java.awt.*; public abstract class GItem3d{

5 6 7 8 9 10 11 12

10.6.4

static public Transformation3d drawingTransformation = new Transformation3d(); public abstract void draw(Graphics g); public abstract void erase(Graphics g); public abstract void transform(Transformation3d trans); public void setDrawingTransformation(Transformation3d in){ drawingTransformation = in; } }// GITEM

Line3d From the point of view of the data model, line doesn’t change much either. •A constructor needs altering to allow the specification of the z

coordinate. Eventually the draw() method must implement the algorithm for drawing projections and for using the viewpoint transformation, but discussion of that (and an example) is deferred to the next section. 10.6.5

Bezier3d The Bezier curve class only needs a change to the addPoint() method to allow the specification of the z coordinate. Like the straight line class above, the draw method must implement the algorithm for drawing projections and for using the viewpoint transformation, but once again, both discussion and examples are deferred to the next section.

177

3D graphics

10.6.6

Transformation3d The various transformation such as the rotations around the axes and the various projection transformations can all be added here.

Example 48. Transformation3d

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46

class Transformation3d extends Matrix{ Transformation3d(){ super(4,4,0); m[0][0]=1; m[1][1]=1; m[2][2]=1; m[3][3]=1; }//constructor

Transformation3d(int in_rows, int in_columns, float val){ super(in_rows,in_columns,val); }//constructor

Transformation3d(float[][] inData){ super(inData); }//constructor

public Object clone() { Transformation3d myClone = new Transformation3d(m); return myClone; }//clone

public void translate(float x, float y, float z) { m[0][3]=x; m[1][3]=y; m[2][3]=z; }//translate

public void scale(float sx, float sy, float sz){ m[0][0]=sx; m[1][1]=sy; m[2][2]=sz; }//scale

public void rotatex(float angle) { m[1][1] = (float)Math.cos(angle); m[2][1] = -(float)Math.sin(angle); m[1][2] = (float)Math.sin(angle); m[2][2] = (float)Math.cos(angle);

178

3D graphics

47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

}//rotate

public void rotatey(float angle) { m[0][0] = (float)Math.cos(angle); m[0][2] = (float)Math.sin(angle); m[2][0] = -(float)Math.sin(angle); m[2][2] = (float)Math.cos(angle); }//rotate

public void rotatez(float angle) { m[0][0] = (float)Math.cos(angle); m[1][0] = -(float)Math.sin(angle); m[0][1] = (float)Math.sin(angle); m[1][1] = (float)Math.cos(angle); }//rotate

public void perspective(float viewingDistance) { m[2][2]=0; m[3][2]=1/viewingDistance; }//perspective

public void isometric() { m[2][2]=0; m[0][0]=(float)Math.sin(Math.toRadians(60)); m[0][1]=-(float)Math.cos(Math.toRadians(60)); m[2][0]=-(float)Math.sin(Math.toRadians(60)); m[2][1]=-(float)Math.cos(Math.toRadians(60)); }//isometric

public Transformation3d mult(Transformation3d b){ float sum = 0; int i,j,k; if (rows != b.columns) { throw new IllegalArgumentException("Matrices are not conformable."); } Transformation3d result = new Transformation3d(b.rows,columns,0); for (i=0; i < b.rows; i++) { for (j=0; j < columns; j++){ sum = 0; for (k=0; k
179

3D graphics

10.7

Implementing 3d - Drawing, Projections and Viewpoints We are now ready to try and implement projections and viewpoint transformations. Let us first take a look at what it is we are trying to achieve. Figure 96shows an array of cubes on a plane viewed from four different points of view.

Figure 96. Screenshot - A field of cubes from various points of view. To summarise, what we need to do is take the 3d data model that is stored in our shapes, lines and points - and apply a series of transformations to it before somehow plotting it on the screen. Those transformations are: •a local rotation of the model about the viewpoints x axis •a local rotation of the model about the viewpoints y axis •a local rotation of the model about the viewpoints z axis •a translation of the model in the x direction by an amount equal to

the (negative) displacement of the viewpoint from the origin in the x direction •a translation of the model in the y direction by an amount equal to the (negative) displacement of the viewpoint from the origin in the y direction •a translation of the model in the z direction by an amount equal to the (negative) displacement of the viewpoint from the origin in the z direction •one of the projection transformations

180

3D graphics

In practise, what we do is concatenate all of those transformations into one (known as the drawing transformation) by multiplying them in the order that they are listed above and calculate the coordinates that result by applying the transformation to each point in the model. The first point to note about implementing all of this is that we don’t want to change the world! The drawing transformation is never applied to the model itself. What we actually do, is when we come to draw each line that makes up the world (eventually each surface) is to make a temporary copy of the points, apply the transformation to the copy and use the resulting points as our coordinates for drawing. Lets us consider where the constituent transformations are set up, where they are concatenated, which objects they are shared with (i.e. which objects can access them), where they are actually used to draw some lines and which parts of our system may change them in order to achieve “movement.” 10.7.1

Setting up the drawing transformation The setting up of the drawing transformation occurs in Gapp, basically because the choice of a particular viewpoint and type of projection is likely to be application dependant. Gapp actually has three transformations one is the drawing transformation one represents the rotation of the viewpoint and the third represents the translation of the viewpoint. This separation of transformations allows independent control of the position and orientation of the viewpoint “camera”.

Example 49. Transformations in Gapp

1 2 3 4 5 6 7 8

public class Gapp extends JFrame implements Runnable{ . . .

9 10 11

10.7.2

Transformation3d viewpointRotation = new Transformation3d(); Transformation3d viewpointTranslation = new Transformation3d(); Transformation3d viewpointTransformation = new Transformation3d(); . . .

Concatenating the transformations The rotation and translation transforms can be concatenated into one viewpoint transformation in the Gapp class. This only needs to be done once - or at least once every time the viewpoint changes.

181

3D graphics

10.7.3

Sharing the drawing transformation It can be seen from the example code in section 10.6.3 above that class has a static variable for holding the drawing transformation. essentially, each Line3d, Bezier3d or Spline3d needs to be able to access the drawingTransformation - this is a space efficient way of doing it. Gitem3d

By using a method which references that static variable, we could set the drawingTransformation by calling that method of any Line3d etc. It makes sense to use the top level of our data structure, i.e. the drawing object as the recipient of that method invocation. So once the concatenation has been done, Gapp calls the setDrawingTransformation() method of the drawing objects and thus sets the transformation to be used by the entire data structure.

Example 50. Gapp

12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

10.7.4

. . . public Gapp(){ setBackground(Color.white); // start point for camera viewpointTranslation.translate(300,00,1000); //tell the drawing what to use as its drawing transformation myDrawing.setDrawingTransformation(viewpointTransformation); myGcanvas.setDrawing(myDrawing); . . .

Using the drawing transformation The drawing of lines on the screen doesn’t move from the 2d case - it is still the draw methods of the line and bezier (and spline) classes that does the actual work of line drawing. The projection transformation is dealt with entirely within the line3d and Bezier 3d class

182

3D graphics

Here we make copies of the endpoints of the line, apply the drawing transformation to those copies (LEAVING THE ORIGINALS UNCHANGED) and use the x and y coordinates of those points as the endpoints of the lines we draw on screen. Note that it is a property of the projection transformations that they will always set the z coordinate to 0.

Example 51. class Line3d - data members, constructor and rawDraw()

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44

class Line3d extends GItem3d implements Cloneable{ private private private private

Point3d src; Point3d dest; Matrix srcDraw = new Matrix(4,1,0); Matrix destDraw= new Matrix(4,1,0);

private Transformation3d perspective = new Transformation3d(); private int viewingDistance = 1000; private Transformation3d isometric = new Transformation3d();

public Line3d (float x1, float y1, float z1, float x2, float y2, float z2) { src = new Point3d(x1,y1,z1); dest = new Point3d(x2,y2,z2); perspective.perspective(viewingDistance); isometric.isometric(); }//end constructor . . . . . . public void rawDraw(Graphics g){ srcDraw = src.mult(drawingTransformation); destDraw = dest.mult(drawingTransformation); float z1 = srcDraw.getElement(2,0); float z2 = destDraw.getElement(2,0); srcDraw = srcDraw.mult(perspective); destDraw = destDraw.mult(perspective); int int int int

x1 y1 x2 y2

= = = =

(int)(srcDraw.getElement(0,0)/ (1+(z1/viewingDistance))); (int)(srcDraw.getElement(1,0)/ (1+(z1/viewingDistance))); (int)(destDraw.getElement(0,0)/ (1+(z2/viewingDistance))); (int)(destDraw.getElement(1,0)/ (1+(z2/viewingDistance)));

g.drawLine(x1, y1, x2, y2);

183

3D graphics

45

}//rawDraw

Example 52. Bezier3d() - data members, constructor and rawDraw()

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50

class Bezier3d extends GItem3d implements Cloneable{ Vector points = new Vector(); int numberOfPoints =0; int segments = 10; float interval = (float)1/segments; private private private private

Point3d src; Point3d dest; Matrix srcDraw = new Matrix(4,1,0); Matrix destDraw= new Matrix(4,1,0);

private Transformation3d perspective = new Transformation3d(); private int viewingDistance = 1000; private Transformation3d isometric = new Transformation3d();

Bezier3d(){ isometric.isometric(); perspective.perspective(viewingDistance); }

public void rawdraw(Graphics g){

float xsrc=((Point3d)points.get(0)).x(); float ysrc=((Point3d)points.get(0)).y(); float zsrc=((Point3d)points.get(0)).z(); for (float float float float

t=0; t<=1.001 ; t+= interval){ xdest=0; ydest=0; zdest=0;

for (int i=0; i
184

3D graphics

51 52 53 54 55

56 57 58 59 60 61 62 63

//use these for isometric projection //srcDraw = src.mult(isometric); //destDraw = dest.mult(isometric); g.drawLine((int)(srcDraw.getElement(0,0)/ (1+(src.z()/ viewingDistance))), (int)(srcDraw.getElement(1,0)/ (1+(src.z()/ viewingDistance))), (int)(destDraw.getElement(0,0)/ (1+(dest.z()/ viewingDistance))), (int)(destDraw.getElement(1,0)/ (1+(dest.z()/ viewingDistance)))); xsrc=xdest; ysrc=ydest; zsrc=zdest; }//for t }//end rawdraw

The action actually happens in the places where lines are drawn Line3d.rawdraw, Bezier3d.rawdrawNote that as implemented here, the projection transformation can’t be changed - we’re stuck with a perspective view and the zoom factor (or viewing distance) can’t be changed either. A better implementation would allow both of these to be controlled from Gapp. 10.7.5

Changing the drawing transformation If we wish to change the viewpoint, it is only necessary to alter the drawing Transformation once (since it is a shared object). We can conveniently do this in Gapp. The example below allows us to transform the viewpoint so that we can “walk” through our model, turning in either direction to see what is to the left and right of us. Pressing the appropriate keys, applies the appropriate transformation to the viewpoint transformation.

Example 53. “Walk” controls in Gapp - part of the KeyListener

1 2 3 4 5 6 7 8 9 10 11 12 13

addKeyListener (new KeyListener() { public void keyTyped(KeyEvent evt) { char myChar = evt.getKeyChar(); System.out.println("Key Typed!" + myChar); switch (myChar) { case 'i' : viewpointTranslation.transform(towards); break; case ' ' : viewpointTranslation=(Transformation3d)identity.clone(); viewpointRotation=(Transformation3d)identity.clone(); break; case 'j' : viewpointTranslation.transform(right); break; case 'd' : viewpointTranslation.transform(down);

185

3D graphics

14 15 16 17 18 19 20 21 22 23

break; case 'e' : viewpointTranslation.transform(up); break; case 'l' : viewpointTranslation.transform(left); break; case ',' : viewpointTranslation.transform(away); break; case 's' :viewpointRotation.transform(clockwise); break; case 'a' : viewpointRotation.transform(anticlockwise);

24 25 26 27

break; }//end switch viewpointTransformation=viewpointTranslation.mult(viewpointRotation);

28 29

myDrawing.setDrawingTransformation(viewpointTransformation);

30 31 32 33 34 35

Aside

repaint(); }//end keyTyped public void keyPressed(KeyEvent evt) {} public void keyReleased(KeyEvent evt) {} });

Note that there is nothing to stop an application having more than one window each with a different viewpoint on the scene. If you’ve ever used the Microsoft Flight Simulator with multiple views, you’ll have seen this in action. To tie all of the various things in this chapter together, Figure 97 shows the class diagram of the system we have developed so far. The corresponding java code can be found in the basic3d_with_viewpoint_transformation example of the online examples in the associated website: http://www.cs.strath.ac.uk/~if/classes/52359

186

3D graphics

Figure 97. Class diagram of the example

187

3D graphics

Chapter review - 10 There is a lot of material in this chapter but it can be broken down as follows •Representing the world with a 3d coordinate system •Extending the 2d data model with the extra z coordinate •Performing transformations in 3d •Projecting a 3d model onto a 2d screen

- perspective projection - parallel projection - isometric projection •Viewing a model from different viewpoints •Implementing all of the above For an example of all this in action - looks at the “Basic3d with viewpoint Transformation” example on the associated website.

188

3D graphics

Exercises - 10 10.1

Develop the simplest possible 3d system by extending your 2d classes. Start by extending them to cope with z coordinates. Implement a transformation for a cabinet projection. Use the classes to display a wireframe drawing of a cube. Do NOT at this stage attempt animation or viewpoint transformation.

10.2

Implement the 3d translation transformation. Use this to reposition your cube in several different places on the screen. See what happens to the projection when you translate in the z and negative z directions.

10.3

Implement a perspective projection. Again, see what happens to the projection when you translate in the z and negative z directions.

10.4

Implement rotation transformations for each axis.

Improving visual realism

189

Chapter 11 Improving visual realism

11.1

Introduction Wireframe drawings of the type we have been producing so far in our consideration of 3d graphics are useful for various tasks and are reasonably “cheap” in terms of the processor power required to produce and manipulate them. In terms of looking realistic (whatever that may mean), they leave something to be desired. One problem when viewing wireframe is misinterpreting which angle the object is being viewed from. Figure 98 shows the problem. Is the cube being viewed from above and too the left or from below and too the right?

Is this the back or the front face of the cube?

Figure 98. Perspective confusion

Improving visual realism

190

To resolve this confusion what are required are depth cues - hints to the eye and visual perception system as to how to view the object. The best way to do this is to depart from the wireframe representation and remove all the lines that would not be seen from a given view point. Figure 99 illustrates this.

Figure 99. Hidden line removal In this chapter we will look at several techniques which can be used to improve the “realism” of the objects we are creating.

11.2

Hidden line removal There is no one best algorithm for hidden line removal (or surface culling as it is sometime known). For a convex solid, a simple approach based upon working out which way a surface is pointing relative to the viewer works well.

Aside

What is a convex solid? A convex solid has the property that a line drawn from any point on one surface to a point on a second surface passes entirely

Improving visual realism

191

through the interior of the solid.

Figure 100. Convex and concave solids In order to use this technique we must abandon our simple way of constructing objects purely from lines. The concept of a surface defined by a series of points is necessary. We will come across a whole variety of different types of surface over the next sections and chapters, but for now we will consider solely flat surfaces.

Figure 101. A pair of flat surfaces defined by points

192

Improving visual realism

They can be placed into our familiar class structure as shown in Figure 102

GItem3d

Matrix

M

Shape3d

1

Surface3d Wireframe

Line3d

Bezier3d

Point3d 1

M

1

1

Transformation3d

M

1

1

1

Drawing3D

JPanel

JFrame

1

Gcanvas

Gapp

paintComponent

1

initComponents thisWindowclosing

1

1

Figure 102. 3d class structure including Surface3dWireframe As implied above, one key requirement of our surfaces is that they are FLAT. The easiest way to ensure this is by using only three points to define the surface, (any polygon with only three defining points MUST be flat think about it) but as long as you promise not to do anything that will bend a flat surface, we can allow them to be defined by as many points as you like.

193

Improving visual realism

How do we work out which way a surface is points? For that matter how do we define which way a surface points, after all it has two sides? To solve this conundrum we turn to vector mathematics. and the concept of a surface’s normal vector. A surface’s normal vector is simply an arrow that is perpendicular to that surface (i.e. it sticks straight out).

Figure 103. Some surfaces and their normal vectors 11.2.1

Determining visibility Now consider the six faces of a cube and their normal vectors

N2 L

N1 surface1 s

fa ur

ce

2

Figure 104. normals to a cube All we need to know is: Is the viewpoint on one side of the surface or the other?

194

Improving visual realism

Vectors N1 and N2 are the normals to surfaces 1 and 2 respectively. Vector L points from surface 1 to the viewpoint. It can be seen that surface 1 is visible to the viewer whilst surface 2 cannot be seen from that position. Mathematically, a surface is visible from the position given by L if: (Equation 45)

0 ≤ θ ≤ 90° where θ is the angle between L and N for the surface. Equivalently: (Equation 46)

0 ≤ cos θ ≤ 1

Fortunately we can calculate cos θ from the direction of L (lx,,ly,,lz) and N nx,ny,,nz). This is due to the well known result in vector mathematics - the dot product (or the scalar product) whereby: L ⋅ N = L ⋅ N ⋅ cos θ

Aside

The vertical bars denote magnitude (i.e. the length of the vector). The length of a vector can be calculated from Pythagoras’ theorem: L =

2

2

2

lx + ly + lz

Alternatively: (Equation 47)

L ⋅ N = cos θ

where L and N are unit vectors (i.e of length 1). So, if L.N is a scalar (i.e. simply a number), how do we work it out?

195

Improving visual realism

(Equation 48)

L ⋅ N = l x n x + l y n y + l z n z = cos θ

At this point, we know that we need to calculate cos θ, we know values for lx,,ly, and lz. The only things we are missing are nx,ny, and nz. 11.2.2

Calculating the normal vector of a surface What we need to do is to use the other way of multiplying two vectors the vector product (or cross product). If you multiply any two vectors using the vector product, the result is another vector that is perpendicular to the plane (i.e normal) which contained the two original vectors. Figure 105 attempts to illustrate this. n=a^b Note:

b

a and b are in the a

same plane

Figure 105. Vector product IMPORTANT - We need to adopt the convention that the calculated normal vector points away from the observer when the angle between the two initial vectors is measured in an clockwise direction. Failure to do this will lead to MAJOR confusion when you try and implement this - believe me, I know.

196

Improving visual realism

The only problem that then remains is where to find two vectors that we can multiply? Answer: we can manufacture them artificially from the points that define the plane we want the normal of.

P1 (x1,y1,z1)

n

a P2

P0

P3

(x0,y0,z0)

b

P4 (x4,y4,z4)

Figure 106. Manufacturing vectors from the points of a plane. By subtracting the coordinates of consecutive points we can form vectors which a guaranteed to lie in the plane of the surface under consideration.

Aside

a = (ax,ay,az) = (x1-x0, y1-y0, z1-z0)

(Equation 49)

b = (bx,by,bz) = (x4-x0, y4-y0, z4-z0)

(Equation 50)

Note there is nothing special about picking P0 as the start point for the vectors - ANY set of three points from the surface would do just as well. We define the vectors to be anti-clockwise, when viewing the surface from the interior (imagine the surface is part of a cube and your looking at it from INSIDE the cube. Following the anticlockwise convention mentioned

197

Improving visual realism

above we have produced what is known as an outward normal. An important consequence of this is that when you define the points that define a surface in a program, you MUST add them in anti-clockwise order.

normal vector is INTO the page

Figure 107. Anti-clockwise order of points and the outward normal Finally, we can work out the vector product thus:

ax bx

nx = aybz - azby

(Equation 51)

ny = azbx - bzax

(Equation 52)

nz = axby - aybx

(Equation 53)

ay by

az bz

az bz

You can remember these using this mnemonic

These values for nx,ny, and nz can be fed back into equation Equation 48, cos θ can be determined and we can final decide whether or not the draw the surface. If desired, instead of merely omitting the surface altogether, it could be drawn with a dashed line which provides an effective depth cue. Actually, in certain cases we can simplify things a little. If the viewpoint lies somewhere on the negative side of z-axis, as it did when we first set up the projection transformations (i.e without any viewpoint transformations) we can forget about L and cos θ. All we really need to know is whether or not the normal points into the screen or out of it, i.e. is nz positive or negative? In that case, all we need to do is calculate Equation 53.

198

Improving visual realism

11.2.3

An alternative approach An alternative approach is consider where the extension of the surface cuts the z-axis - in the negative region - the surface must be tilted towards the viewer or in the positive region, the surface must be tilted away. surface

z extension of surface z=0

Figure 108. Visibility determination by surface extension

11.3

Implementing hidden line removal A new class Surface3dWireframe is required for this technique. It is implemented as a Vector of lines. Most of the calculation required to determine visibility takes place in this class. The vector arithmetic however can be implemented in one of two places. You can either define a new class Vector3d, and implement it all in there or you can make use of the fact that a points and vectors are more or less equivalent, and place all of the vector maths, such as routines to find the scalar and vector products, a routine for calculating the length of a vector and a routine for normalising a vector (i.e reducing it to a length of 1 but keeping it pointing in the same direction) in the Point3d class. A third possibility is to use a bit of “syntactic sugar” place all the vector routines in the Point3d class and define a Vector3d class which inherits from Point3d but doesn’t override or add to Point3d in any way. Whatever you do, please don’t confuse the JFC Vector class with anything to do with geometrical vectors, it isn’t, it’s just badly named.

Example 54. Point3d class with vector handling methods

1 2 3 4 5 6 7 8

class Point3d extends Matrix implements Cloneable{ public Point3d(float in_x, float in_y, float in_z) { super(4,1,1); m[0][0]=in_x; m[1][0]=in_y; m[2][0]=in_z; }// end constructor

Improving visual realism

9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64

199

public Object clone() { Point3d myClone = new Point3d(m[0][0],m[1][0],m[2][0]); return myClone; }// end clone

public float x() { return (m[0][0]); }

public float y() { return (m[1][0]); }

public float z() { return (m[2][0]); }

public void setx(float x){ m[0][0] = x; }

public void sety(float y){ m[1][0] = y; }

public void setz(float z){ m[2][0] = z; }

public float length(){ return (float)Math.sqrt(Math.pow(m[0][0],2) + Math.pow(m[1][0],2) + Math.pow(m[2][0],2)); }//length

public Point3d normal() { Point3d result = new Point3d(0,0,0); float size = length(); result.setx(x()/size); result.sety(y()/size); result.setz(z()/size); return(result); }//normal

public Point3d minus(Point3d b){ Point3d result = new Point3d(0,0,0); result.setx( x() - b.x() );

200

Improving visual realism

65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102

result.sety( y() - b.y() ); result.setz( z() - b.z() ); return result; }//minus

public Point3d plus(Point3d b){ Point3d result = new Point3d(0,0,0); result.setx( x() + b.x() ); result.sety( y() + b.y() ); result.setz( z() + b.z() ); return result; }//plus

public Point3d vec(Point3d b){ Point3d result = new Point3d(0,0,0); result.setx( (y() * b.z() ) - (z()* b.y()) ); result.sety( (z() * b.x() ) - (x()* b.z()) ); result.setz( (x() * b.y() ) - (y()* b.x()) ); return result; }//vector product

public float dot(Point3d b){ return((x() * b.x()) + (y() * b.y()) + (z() * b.z())); }//dot product

public Point3d scalarMultiply(float sf){ Point3d result = new Point3d(0,0,0); result.setx(x() * sf); result.sety(y() * sf); result.setz(z() * sf); return result; }//vector multiply this object }//end class Point3d

The Surface3dWireframe class implements the storage and drawing of a surface as a collection of lines. In addition, it defines a method visible() which is used to determine whether or not a surface should be displayed. The implementation given here makes the assumption that the viewpoint is on the z-axis, i.e. it DOES NOT allow for viewpoint transformation.

Example 55. Surface3dWireframe

1 2 3 4 5

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

class Surface3dwireframe extends GItem3d implements Cloneable{

Improving visual realism

6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60

int numberOfLines = 0; Vector lines = new Vector(); Surface3dwireframe(){ } public Object clone() { Surface3dwireframe myClone = new Surface3dwireframe(); for (int i=0; i
public String toString() { String result = new String(); for (int i=0; i
public void addLine(Line3d inLine){ lines.add(inLine); numberOfLines++; }//end addLine

public void addLine(float sx, float sy, float sz, float dx, float dy, float dz){ addLine(new Line3d(sx,sy,sz,dx,dy,dz)); }//end addLine

public void rawdraw(Graphics g){ //System.out.println("drawing"); for (int i=0; i
public void draw(Graphics g){ if (visible()){ g.setColor(Color.blue); rawdraw(g); } }//draw

public void erase(Graphics g){ g.setColor(new Color(255,255,255)); rawdraw(g); }//draw

201

202

Improving visual realism

61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90

public void transform(Transformation3d trans) { for (int i=0; i < numberOfLines; i = i+1) { ((Line3d)lines.get(i)).transform(trans); }//end for i }//end transform

public boolean visible() { return(normal().z() < 0); }//visible

public Point3d normal(){ // manufacture 2 vectors in the plane of the surface Point3d k = ((Line3d)lines.get(1)).asVector(); Point3d j = ((Line3d)lines.get(0)).asVector(); //take their vector product to get the normal Point3d result = j.vec(k); //turn it into a unit vector result = result.normal(); return (result); }//normal

}//end class Surface3d

For a complete example of hidden line removal in a wireframe drawing see the “basic3d with wireframe (and hidden surface removal)” example on the associated website at: http://www.cs.strath.ac.uk/if/classes/52.359

203

Improving visual realism

11.4

More complex shapes The above discussion applies only to convex shapes. More complex objects come in two forms: multiple objects and concave objects

Figure 109. More complex objects In these cases, each surface must be considered individually. Two different types of approach are possible: •Object space algorithms - examine each face in space to determine

it visibility •Image space algorithms - at each screen pixel position, determine

which face element is visible. Approximately, the relative efficiency of an image space algorithm increases with the complexity of the scene being represented, but often the drawing can be simplified for convex objects by removing surfaces which are invisible even for a single object. 11.4.1

The Painter’s algorithm The Painter’s algorithm (also known as the depth sort or z-buffer algorithm) is simple in principle and can be summarised thus: Draw the objects furthest away from the viewer first, then progressively add objects in decreasing order of distance. This approach is based upon sorting the surfaces by their z-coordinates. The algorithm can be summarised thus: •Sort the surfaces into order of increasing depth. Define the maxi-

mum z value of the surface and the z-extent.

204

Improving visual realism

•resolve any depth ambiguities •draw all the surfaces starting with the largest z-value z zmax z-extent

x

Figure 110. Maximum z and z-extent of a surface - plan view Ambiguities arise when the z-extents of two surfaces overlap. z surface 2 surface 1 x

Figure 111. Overlapping z-extents - plan view

205

Improving visual realism

y

x

Figure 112. Depth ambiguities - front elevation

11.4.1.1

Resolving ambiguities Fortunately, an algorithm exists for ambiguity resolution. z Q

P x

Figure 113. Resolving ambiguities Where two shapes P and Q have overlapping z-extents, perform the following 5 tests (in sequence of increasing complexity). If any test fails, draw P first.

206

Improving visual realism

•x - extents overlap?. z Q P

no they don’t, test fails x

•y -extents overlap?. z Q P

no they don’t, test fails y

•Q is not completely on the side of P nearest the viewer. z yes it is, test fails P

Q x

•P is not completely on the side of Q further from the viewer. z Q

P

yes it is, test fails

x

207

Improving visual realism

•The projection of the two surfaces overlap. Q

y

z

P

Q P

hole in P x

x no they don’t, test fails

If all tests are passed, then reverse P and Q in the list of surfaces sorted by Zmax and set a flag to say that the test has been performed once.The flag is necessary for the case of intersecting planes. If the tests are all passed a second time, then it is necessary to split the surfaces and repeat the algorithm on the 4 surfaces. (end up drawing Q2,P1,P2,Q1. z P1

Q2 P2

Q1 x

208

Improving visual realism

Chapter review - 11 This chapter has highlighted the need for depth cues and hidden line removal to improve the visual realism of rendered scenes. This can be achieved in several ways: •Using surfaces rather than lines/wireframe drawings •Not showing surfaces/objects which cannot be seen from the

scene’s viewpoint (Hidden line/hidden surface removal). •Several approaches to determining visibility can be taken - Calculating which way a surface is facing (surface normal) - Base a decision on visibility on normal vector and observer vector - Painter’s (or z-buffer) algorithm

209

Improving visual realism

Exercises - 11 11.1

Write a Java program that displays a cube as a wireframe drawing. Adapt the program to implement hidden line removal so that only faces point towards the viewer are drawn.

11.2

Further adapt that program do display the faces of the cube as areas of solid colour. You will need to draw the surfaces as filled polygons.

11.3

Display an group of cubes with hidden surface removal (similar to that in Figure 96). Allow the group to be rotated. Implement the painter’s algorithm to ensure correct visibility.

Rendering, Shading and Colour

210

Chapter 12 Rendering, Shading and Colour

12.1

Introduction By introducing hidden line removal we have already taken one step away from wireframe drawings towards being able to realistically model and display 3d objects. Perhaps the biggest step down that road comes when attempting to “colour in” our simple line drawings. The various algorithms for rendering, the process of applying lighting, colouring, shadow and texture to an object or scene in order to obtain a realistic image, are all based to a greater or lesser extent on the study of the physical properties of light. In this chapter we shall examine various properties of light and the way it interacts with objects and develop some simple mathematical models of its behaviour. It is worth setting the following discussion in the context of our system as developed so far. Currently our 3d model is made up of surfaces, each of which we represent on the screen by drawing its outline. It we wanted to shade each polygon (“colour it in”) what colour would we use? What we basically are trying to achieve in this chapter is to derive a method for calculating that colour. Figure 114 shows the difference between a wireframe representation and a simple rendered version.

Rendering, Shading and Colour

211

Figure 114. Wireframe representation and rendered representation

12.2

Illumination The colour or shade that an surface appears to the human eye depends primarily on three factors: •Colour and strength of incoming illumination •Colour and texture (rough/smooth) of surface •Relative positions and orientations of surface, light source and

observer The physiological response of the eye also has a great bearing on the appearance but that is beyond the scope of this book.

212

Rendering, Shading and Colour

12.2.1

Simplifying assumptions

12.2.1.1

Neglect colour - consider intensity For now we shall forget about colour (we’ll return to that later) and restrict our discussion just to the intensity of light. We shall consider white light where the intensity of all colour components (red, green and blue) is equal. This will give us a monochrome (black and white) picture.

12.2.1.2

Intensity and the 1/r2 effect Usually a light source is a mixture of diffuse background illumination and one or more point sources of light. For the purpose of this discussion (mainly to simplify the mathematics) we shall assume that the light source we are considering is at a large distance from the scene. This has two effects: •All light rays from the source are (virtually) parallel •There is no change in the intensity of the light across the scene -

(i.e. there is no fall off of intensity as a 1/r2 effect.

cube A

cube B

ra rb

Figure 115. Difference between close and distance light sources Figure 115 shows the difference between close and distance illumination. In this simple diagram, the intensity of light falling on a surface can be thought of simply as the number of rays which hit it. It can be seen that more rays fall on cube A than on cube B and it can quite easily be shown that the

213

Rendering, Shading and Colour

number is proportional to the reciprocal of the square of the distance between the light source and the object. (i.e 1/r2 where r is the distance between source and surface). 12.2.1.3

Parallel rays It can also be seen in Figure 115 that the light rays crossing cube B are nearly parallel whereas the rays crossing cube a are highly divergent. This means for distant illumination, there is little variation in intensity between one side of an object and the other (which means we only need to do one calculation of intensity for the whole surface), whereas this is not true for close illumination. If the need exists to implement a physically accurate illumination model, we could not make this assumption and would have to take account of these effects, but for most purposes, the simple model will suffice.

12.2.2

Components of illumination Consider now the light reaching the eye of an observer of a scene:

surface

Figure 116. Basic illumination The light reaching the eye when looking at a surface has clearly come from a source (or sources) of illumination and bounced off the surface. In fact the light reaching the eye can be considered as being made up of 3 different components: •that from diffuse illumination (incident rays come from all over

not just one direction) •that from a point source which is scattered diffusely from the sur-

face •that from a point source which is specularly reflected. We will consider each of these components separately and them combine them into one

214

Rendering, Shading and Colour

12.2.3

Diffuse illumination Diffuse illumination means light that comes from all directions not from one particular source. Thinks about the light of a grey cloudy day as compared to a bright sunny one: On a cloudy day, there are no shadows cast, the light from the sun is scattered by the clouds and seems to come equally from all directions

surface

Figure 117. Diffuse illumination Some proportion of the light reaching our surface is reflected back to the observer. That proportion is dependant simply on the properties (colour) of the surface and HAS NO DEPENDANCE on the angle of the viewer (that is why its diffuse!). If the strength of the incident illumination is Id and the observed intensity is Ed then the two are related by the simple formula Ed = R.Id

(Equation 54)

where: •R is the reflection coefficient of the surface (0 <= R <=1) R is the

proportion of the light that is reflected back out) (Box example white: R=1, black: R = 0, gray: R = 0.5). Diffuse illumination alone does not give visual realism. With no angular dependence in the light, the observer will see no difference between a sphere and a disc.

215

Rendering, Shading and Colour

12.2.4

Diffuse scattering from a point source When a light ray strikes a surface it is scattered diffusely (i.e. in all directions):

L

N i

surface

Figure 118. Diffuse scattering from a point source The intensity of the reflected rays is given by: Esd = R.cos(i).Is

(Equation 55)

where: •i is the angle of incidence i.e. the angle between the surface normal

and the ray of light •0 <= i <=90 (if i > 90 the surface is invisible) •Esd is the intensity of the scattered diffuse rays •Is is the intensity of the incident light ray.

Aside

Note - Esd doesn’t change with the angle the observer is looking from. It doesn’t matter where you are looking at the surface from - that’s what diffuse means.

216

Rendering, Shading and Colour

12.2.5

Specular reflection The relationship between a ray of light from a point source to the reflected ray coming from a surface is given by Lambert’s law. L, N and S are all in the same plane

N

L i

S

r

s

O surface

O not necessarily in the same plane

Figure 119. Lambert’s Law i=r

(Equation 56)

- where •i is the angle of the incident ray to the normal of the reflecting sur-

face •r is the angle of the reflected ray. For a perfect reflector, all the incident light would be reflected back out in the direction of S. In fact, when the light strikes a surface it is scattered diffusely (i.e. in all directions): For an observer viewing as an angle (s) to the reflection direction S, some fraction of the reflected light is still visible (due to the fact that the surface isn’t a perfect reflector - some degree of diffusion takes place). The amount of proportion of light visible is a function of the angle s (in fact it is proportional to cos (s)). It also depends on the quality of the surface and the angle

217

Rendering, Shading and Colour

of incidence i. We can define a coefficient w(i) - the specular reflection coefficient - which is a function of the material of which the surface is made and of i. Each surface will have its own w(i). w(i) 1 metal 0.5

glass

0 0

90°

i

Figure 120. Specular reflection coefficient - w(i) Putting all of this together gives: Ess = w(i) . cosn(s) . Is

(Equation 57)

where: •Ess is the intensity of the light ray in the direction of O •n is a fudge factor: n=1 - rough surface (paper) n=10 smooth sur-

face (glass) •w(i) is usually never calculated - simply choose a constant (0.5?). It is actually derived from the physical properties of the material from which the surface is made.

218

Rendering, Shading and Colour

Equation 57 contains the term cosn(s) This is in fact a fudge which has no basis in physics, but works to produce reasonable results. By raising cos(s) to the power n, what we do is control how much the reflected ray spreads out as it leaves the surface. cosn(s) 1 n=1

n=10

s

0

N

L

S n=10

i n=1 surface

Figure 121. Controlling the reflection - the action of n 12.2.6

Combining the illumination Combining all three components (diffuse illumination, diffuse reflection from a point source and specular reflection from a point source) gives us the following expression: E= Ed + Esd + Ess

(Equation 58)

E = R.Id + (R.cos(i) + w(i) . cosn(s)) . Is

(Equation 59)

or written out in full:

where: •E is the total intensity of light seen reflected from the surface by

the observer.

Rendering, Shading and Colour

12.2.7

219

Calculating E A little more work is needed before we can use Equation 59. Let us just examine the various terms to see what we know. •E - We’re trying to calculate E, so obviously that is unknown. •R - is defined for each surface, so we need to add it as a variable to

our surface class and define it when creating the surface, so its known. •Id - The incident diffuse light - we can define this to be anything we like; 0 = darkness, for an 8-bit greyscale 255 = white. - Known •cos(i) - we can work this out from L.N - its the same calculation we did to determine surface visibility in section 11.2.1 - Known. •w(i) - we can define this to be anything between 0 and 1 - trial and

error called for! - Known •n - is defined for each surface, so we need to add it as a variable to our surface class and define it when creating the surface, so, basically its known. •Is - the incident point light source - again we can define this to be

anything we like; 0 = darkness, for an 8-bit greyscale 255 = white. See below for a discussion of adding lights to our data model. Known. •cos (s) - Ah! - problem, not known. i.e. we don’t know s 12.2.7.1

Calculating cos(s) Actually, we don’t try and work out s - we don’t need it, what we do need is cos(s). We rely of the same piece of maths as we did when working out L.N - for testing surface visibility - the dot product. Recall that the angle θ between any two unit vectors (A and B) is related to the vectors by A.B = cos θ. We can thus see from ## that cos(s) = S.O. Work out S and O and we will have the required cos(s). O isn’t to hard to find. Remember its the vector from the surface to the observer. To be precise, its the vector from the centroid of the surface. We know where the viewpoint is (from the system’s viewpoint vector) and we know where the surface is (it’s in our data structure), so if we calculate the centroid, a bit of vector subtraction will give us O. The centroid of a surface can be found by separately summing the x,y and z coordinates of the surfaces defining points and dividing each sum by the number of points. (effectively by finding the average of the surfaces points.):

220

Rendering, Shading and Colour

∑ xi i=0 x c = -----------N

∑ yi i=0 y c = -----------N

∑z =0 z c = i----------N

where: •N is the number of points that define the surface.

Thanks to Lambert’s law we know that S is the mirror of the incident ray about the surface normal. It can be found from some vector maths as shown in Figure 122.

N Q

Q

N.cos i

L i

S

Figure 122. Finding S S = 2Q - L

(Equation 60)

Finally, we know all of the terms in Equation 59 and for any surface in our model we can calculate the appropriate shade.

221

Rendering, Shading and Colour

12.2.8

Implementing illumination A program which implements this model of shading is said to implement Lambert Shading. To do this, we shall require another class to represent a surface: Surface3dFlat. It’s place in our class structure is given in Figure 123.

GItem3d

Matrix

M

Shape3d

1

Surface3dFlat

1

Bezier3d

Line3d

Surface3dWireframe

M

1 M

1 1 1

Point3d

Transformation3d

1

Drawing3D

JPanel

JFrame

1

Gcanvas

Gapp

paintComponent

1

initComponents thisWindowclosing

1

1

Figure 123. Graphics class structure including Surface3dFlat

Rendering, Shading and Colour

222

It is very similar to the Surface3dWireframe of the last chapter except that it stores it vertices as points rather than lines and that when drawing a surface it does it by drawing a polygon that joins all of the projected points rather than by just lines. The vital ingredient of the class is of course the fact that is calculates the colour of that polygon as described above.

Example 56. Surface3dFlat

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

import java.awt.*; import java.util.*; class Surface3d implements GItem3d{ int numberOfPoints = 0; Vector points = new Vector(); int segments = 10; float interval = (float)1/segments; boolean debug = false; private private private private

Point3d src; Point3d dest; Matrix srcDraw = new Matrix(4,1,0); Matrix destDraw= new Matrix(4,1,0);

private private private private

Transformation3d perspective = new Transformation3d(); int viewingDistance = 1000; Transformation3d isometric = new Transformation3d(); Transformation3d offset = new Transformation3d();

Surface3d(){ isometric.isometric(); perspective.perspective(viewingDistance); offset.translate(150,150,0); }

public void addPoint(float x, float y, float z){ points.add(new Point3d(x,y,z)); numberOfPoints++; }//end addPoint

public Point3d getCentroid(){ Point3d centroid = new Point3d(0,0,0); for (int i=0; i
223

Rendering, Shading and Colour

43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67

}//for i centroid.setx (centroid.x()/numberOfPoints); centroid.sety (centroid.y()/numberOfPoints); centroid.setz (centroid.z()/numberOfPoints); return(centroid); }//getCentroid

public void rawdraw(Graphics g){ int int

[] x = new int [numberOfPoints]; [] y = new int [numberOfPoints];

float xsrc=((Point3d)points.get(0)).x(); float ysrc=((Point3d)points.get(0)).y(); float zsrc=((Point3d)points.get(0)).z(); float xdest=0; float ydest=0; float zdest=0; for (int i=0; i
68

ydest = ((Point3d)points.get(i % numberOfPoints)).y();

69

zdest = ((Point3d)points.get(i % numberOfPoints)).z();

70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94

// do projection src=new Point3d(xsrc,ysrc,zsrc); dest= new Point3d(xdest,ydest,zdest); srcDraw = src.mult(offset); destDraw = dest.mult(offset); srcDraw.transform(perspective); destDraw.transform(perspective); x[i]=(int)(destDraw.getElement(0,0)/ (1+(dest.z()/ viewingDistance))); y[i]=(int)(destDraw.getElement(1,0)/ (1+(dest.z()/ viewingDistance))); xsrc=xdest; ysrc=ydest; zsrc=zdest; } //for i g.fillPolygon(x,y,numberOfPoints); }//end rawdraw

public int shade(){ // assume one point light source and general diffuse light Point3d light = new Point3d(0,0,0); Point3d normal = normal();

224

Rendering, Shading and Colour

95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150

// light = vector of incident ray from point source at point l to centroid // light source (l) is located at (0,0,10) Point3d l = new Point3d(0,0,-500); light = getCentroid().minus(l); // a-b = b -> a light = light.normal(); Point3d minuslight = new Point3d(0,0,0); minuslight = minuslight.minus(light); // cosi is what we need float cosi = minuslight.dot(normal()); // // // //

s = vector of reflected ray currently requires that the light sourcec be on the z axis Should be sx,sy,sz

float sx = 2 * normal.z() * normal.x(); float sy = 2 * normal.z() * normal.y(); float sz = (float) (2 * (Math.pow(normal.z(),2))); Point3d Point3d Point3d Point3d

ncosi = normal.scalarMultiply(cosi); q = ncosi.plus(light); q2 = q.scalarMultiply(2); s = q2.minus(light);

s = s.normal(); // observer is the vector of the centre of projection Point3d observer= new Point3d(0,0,-1); // coss is what we need float coss = s.dot(observer);

float r = 0.8f; // reflection coefficient of surface float w = 0.8f; // specular reflection coeffient float n = 10f; // fudge roughness of the surface // n = 10 : smooth - glass // n = 1 : rough - newspaper float diffuse = r * cosi; float reflected = w * (float) Math.pow(coss,n); int col = 55 + (int) Math.round( (reflected) * 200); return(col) ; }//shade

public void draw(Graphics g){ if (visible()){ int col = shade();

225

Rendering, Shading and Colour

151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194

12.2.9

try { g.setColor(new Color(col,col,0)); } catch (Exception e) { System.out.println("Surface3d.draw: calculated colour(" + col + ") out of range"); g.setColor(Color.red); } rawdraw(g); } }//draw

public void erase(Graphics g){ g.setColor(new Color(255,255,255)); rawdraw(g); }//erase

public void transform(Transformation3d trans) { for (int i=0; i < numberOfPoints; i = i+1) { ((Point3d)points.get(i)).transform(trans); }//end for i }//end transform

public boolean visible() { return(normal().z() < 0); }//visible

public Point3d normal(){ Point3d k = ((Point3d)points.get(2)).minus((Point3d)points.get(1)); Point3d j = ((Point3d)points.get(0)).minus((Point3d)points.get(1)); Point3d result = j.vec(k); result = result.normal(); return (result); }//normal

}//end class Surface3d

Extending to colour As implemented above, the shading model is monochrome (greyscale, black and white). It can (and should) be converted to full colour quite easily. Any light source has a colour which can be expressed in terms of its red, green and blue components.

226

Rendering, Shading and Colour

Each surface has a colour which means it reflects different colours by different amounts, i.e. it has a different value of R for red, blue and green (Rr,Rg,Rb). This means that to arrive at a final colour with which to shade a surface, we must evaluate Equation 58 separately for red, green and blue components.

12.3

Ered= Edr + Esdr + Essr

(Equation 61)

Egreen= Edg + Esdg + Essg

(Equation 62)

Eblue= Edb + Esdb + Essb

(Equation 63)

Approximating smooth surfaces with polygon nets. Networks of polygons are used to represent smooth surfaces. They are, of course, only an approximation to the true surface, but they are adequate for most computer graphics tasks. One of the commonest uses of the technique is approximate to a cylinder by the use of a set of flat polygons: The Lambert shading model is a great improvement over wireframe drawings as far as realism is concerned. It is also a reasonably fast technique when compared to some of the alternatives that we are about to meet. It is, however, far from perfect. Using a different colour for each polygon means that the polygons show up very clearly (the appearance of the model is said to be faceted). This is fine for a cube, but not so good when attempting to use the polygon as part of a smooth surface. Figure 124 shows 32 flat surfaces, arranges in a circle, pretending to be a cylinder. The difference in intensity between neighbouring sections can easily be seen.

Figure 124. Faceted appearance.

Rendering, Shading and Colour

227

•The problem is made worse by a phenomena known as Mach

bands. This is a physiological effect whereby the contrast between two areas of a different shade is enhanced as the border between the shades. If you stare closely at the boundary of two neighbouring surfaces in Figure 124, you can see that the join appears to be slightly lighter than the surfaces.

actual intensity

perceived intensity

Figure 125. Mach banding Aside

This is only a problem when the polygons concerned are part of what is meant to be a smooth surface. There are, of course, many occasion when two surfaces are not meant to join smoothly. What is required is some means of smoothing the sudden transition in colour. Various algorithms exist for this; amongst the best known are Gouraud shading and Phong shading - both named after their inventors.

Gouraud shading The faceted appearance of a Lambert shaded model is due to each polygon having only a single colour. To avoid this effect, it is necessary to vary the colour across a polygon:

228

Rendering, Shading and Colour

Figure 126. Variable shading of an individual polygon In order to achieve this, the colour must be calculated for each pixel instead of one colour for the entire polygon. By ensuring that the method we use to calculate the colour results in the neighbouring pixels across the border between two polygons end up with approximately the same colours, we will be able to blend the shades of the two polygons and avoid the sudden discontinuity at the border. Lambert shading is based upon calculating a single normal vector for the surface (which is then compared to the lighting vector and the viewpoint vector to determine the colour). Gouraud shading is based upon calculating a vertex normal rather than a surface normal. A vertex normal is an artificial construct (a true normal cannot exist for a point such as a vertex). A vertex normal can be thought of as the average of the normals of all the polygons that share that vertex. surface normals

n4

n3 nv

n2 n1

vertex normal

Figure 127. A vertex normal

229

Rendering, Shading and Colour

nv can be calculated from: N

∑ ni i=1 n v = ---------------N

(Equation 64)

∑ ni i=1

- where N is the number of polygons sharing the vertex. Having found the vertex normals for each vertex of the polygon we want to shade,(Figure 128)

Figure 128. Find the vertex normal at each vertex we can calculate the colour at each vertex using the same formula that we did for Lambert Shading. Calculating the colour for all the remaining pixels in the polygon is simply a matter of interpolating from the vertices. i.e. if your

230

Rendering, Shading and Colour

half-way along one of the edges, the colour value needs to be halfway between the colour values at the ends of the edge. A value for the colour can be given more formally by considering a scan-line through the polygon.

C

yscan

S2 S1

P B A

Figure 129. find the vertex normal at each vertex If we stick simply to gray-scale values, the intensity of light at point P (Ip) can be calculated thus: y c – y scan I s1 = I c – ( I c – I a ) ⋅ ---------------------yc – ya

: y c – y scan I s2 = I c – ( I c – I b ) ⋅ ---------------------yc – yb

x s2 – xp I P = I s2 – ( I s2 – I s1 ) ⋅ ----------------xs2 – xs1

By performing 3 separate calculations, one for red, one for green and one for blue, a complete colour value can be achieved.

231

Rendering, Shading and Colour

12.4

Phong Shading Phong shading too is based on interpolation, but instead of interpolating the colour value, it is the normal vector which is interpolated for each point and a colour value calculated for each pixel based on the interpolated value of the normal vector. The interpolation is (like Gouraud shading) based upon calculating the vertex normals (red arrows in Figure 130), using these as the basis for interpolation along the polygon edges (blue arrows) and then using these as the basis for interpolating along a scan line to produce the internal normals (green vectors).

Figure 130. Stages of interpolation in Phong shading Phong shading allows us to counteract the fact that we are using a flat surface to approximate to a curved one. The arrows (and thus the interpolated vectors) give an indication of the curvature of the smooth surface which the flat polygon is approximating to. 12.4.1

Comparison of Gouraud and Phong Phong shading is requires more calculations, but produces better results for specular reflection than Gouraud shading in the form of more realistic highlights. Consider the specular reflection term in Equation 59: cosn s

Rendering, Shading and Colour

232

If n is large (the surface is a good smooth reflector) and one vertex has a very small value of s (it is reflecting the light ray in the direction of the observer) whilst the rest of the vertices have large values of s - a highlight occurs somewhere on our polygon. With Gouraud shading, nowhere on the polygon can have a brighter colour (i.e higher value) than a vertex so unless the highlight occurs on or near a vertex, it will be missed out altogether. When it is near a vertex, its effect is spread over the whole polygon. With Phong shading however, an internal point may indeed have a higher value than a vertex. and the highlight will occur tightly focused in the (approximately) correct position.

Figure 131. Comparison of highlights with Gouraud and Phong shading.

Rendering, Shading and Colour

233

Chapter review - 12 This chapter has examined the manner in which a greater understanding of the properties of light can lead to a more realistic approach to rendering a scene and considered the pros and cons of various shading algorithms •Lambert shading leads to a faceted appearance •To get round this, use a smooth shading algorithm •Gouraud and Phong shading produce good effects but at the cost of

more calculations. •Gouraud interpolates the calculated vertex colours •Phong interpolates the calculated vertex normals •Phong – slower but better highlights

Rendering, Shading and Colour

234

Exercises - 12 12.1

Develop a Lambert shading routine to fit into the program developed for Exercise 11.2

12.2

Add Gouraud shading

12.3

Add Phong shading.

Fine detail - Texture Mapping and Bump Mapping

235

Chapter 13 Fine detail - Texture Mapping and Bump Mapping

13.1

Introduction With the use of hidden surface removal and the various rendering techniques, we have reached the stage where we can construct models and scenes that are recognisable: an observer can usually work out what it is that they are looking at. It would be possible, with a lot of time and patience to model even the fine detail of a scene. Take this image of a block of wood for example. It would be possible to model every grain every knot etc. using the techniques we have covered so far.

Fine detail - Texture Mapping and Bump Mapping

236

Figure 132. A block of wood Producing such models (explicit modelling) is extremely difficult and time consuming. It is better, both from a computational efficiency point of view and in terms of keeping the modeller sane, to cheat and use an approximation to the fine detail. Two techniques in particular offer a great increase in realism for minimal additional programming effort: texture mapping and bump mapping.

Fine detail - Texture Mapping and Bump Mapping

13.2

237

Texture mapping The idea behind texture mapping is that it allows us to ‘stick’ a picture onto a surface. For instance we can easily transform this box into a wall simply by sticking a picture of some bricks onto the basic shape. As the basic shape is transformed, so is the picture

Figure 133. Building a wall 13.2.1

Modulating the colour of the surface We wish to determine colour on a pixel by pixel basis, rather than adopting one colour for the whole surface. When performing Phong or Gouraud shading, we already have to calculate the colour with which to paint each pixel. At the moment the colour is worked out from: Er = Rr.Id + (Rr.cos(i) + w(i) . cosn(s)) . Is Eg = Rg.Id + (Rg.cos(i) + w(i) . cosn(s)) . Is Eb = Rb.Id + (Rb.cos(i) + w(i) . cosn(s)) . Is

(These are just the colour versions of Equation 59 - see section 12.2.6) All we need to do is to replace the R terms in these equations (which are constant for every pixel on a surface) with values retrieved from an image (often known in these circumstances as a texture map) - which can be different for each pixel on the surface.

Fine detail - Texture Mapping and Bump Mapping

13.2.2

238

Mapping pictures onto surfaces What we need is a means a sticking the picture onto the surface. One possible approach would be to treat each pixel in the texture map as a point and transform it along with the surface. In fact a much simpler process is to work out how to deform the image so that it fits into the projected polygon that results after all the various transformations. There are ways and means of mapping any shape of image onto an arbitrary polygon, but for the sake of simplicity, we shall stick to mapping a rectangular image onto a four sided polygon.

Projection Coordinates

Image coordinates

Figure 134. Mapping an image onto a polygon

239

Fine detail - Texture Mapping and Bump Mapping

. a b A

B

S1

D

P

yscan s1

S2

p

s2 Projection Coordinates

C Image coordinates

d c

Figure 135. Mapping an image onto a polygon It can be seen from Figure 134, that any pixel in coordinates space can be mapped back to a pixel in image space. There exist numerous elegant and sophisticated methods for doing this quickly and with a minimum of visual distortion. We shall content ourselves with one that is easy to explain. The coordinates of P on the texture map can be derived by considering the ratios of the various lengths indicated: AS1:AC = as1:ac BS2:BC = bs2:bc S1P:S1S2 = s1p:s1s2

so S1x = (Cx-Ax). (s1x-ax/cx-ax) S1y = (Cy-Ay). (s1y-ay/cy-ay) and S2x = (Cx-Bx). (s2x-bx/cx-bx) S2y = (Cy-By). (s2y-by/cy-by)

Fine detail - Texture Mapping and Bump Mapping

240

Thus: Px = (S2x-S1x) . (s2x-px/s2x-s1x) Py = (S2y-S1y) . (s2y-py/s2y-s1y)

Rpred - red reflectivity at point p is equal to the red value in the texture map at point P Rgreen - red reflectivity at point p is equal to the red value in the texture map at point P Rblue - red reflectivity at point p is equal to the red value in the texture map at point P

13.3

Tilling It is possible to adjust the number of times that the texture map is to fit onto the surface:

Figure 136. tiled texture example

13.4

Bump mapping With texture mapping, we modulated the colour of the surface of a pixel by pixel basis. It can be seen from Equation 59 that there are other parameters that we could modulate:

241

Fine detail - Texture Mapping and Bump Mapping

By creating another map (the bump map) which modulates the surface normal vector, we can simulate the effect of a small local deformation in the surface in other words, a bump. This allows us to roughen a surface, add small details etc.

darker

lighter

modulating N

L i

r

S s

O surface

Figure 137. Effect of artificially altering the surface normal vector Figure 137 shows how bump mapping achieves its effect. By artificially altering the surface normal vector, we alter the direction of the S vector and hence the amount of light reaching the observer. Exactly the same algorithm for mapping an image onto the surface is used as in texture mapping, but this time the value retrieved from the image represents a change in the i and r angles rather than colour information.

Fine detail - Texture Mapping and Bump Mapping

13.5

242

Some examples of texture and bump mapping The skilful use of this technique along with good scene lighting can produce startlingly realistic results.

Figure 138. Texture mapping, bump mapping and good lighting.1

1. Illustrations by kind permission of W.L. Arance. See http://www.arance.net/textures.htm for some excellent examples of the art of bump and texture mapping.

Fine detail - Texture Mapping and Bump Mapping

243

Chapter review - 13 In this chapter you will have seen: •the need to model fine details to achieve realism •that explicitly modelling fine details is difficult and time consum-

ing. •fine detail can be simulated using texture mapping and bump map-

ping •texture mapping works by modulating the surface colour term in the shading equation •bump mapping works by modulating the surface normal vector in

the shading equation.

Fine detail - Texture Mapping and Bump Mapping

244

Exercises - 13 13.1

Using the approach of Section 13.2.2, produce a program which can display a .gif file in an arbitrary quadrilateral.

13.2

Using your answer to either Exercise 12.2 or Exercise 12.3 as a basis, incorporate the answer to Exercise 13.1 and implement texture mapping.

245

Ray Tracing

Chapter 14 Ray Tracing

14.1

Introduction The lighting, rendering and texturing techniques of the preceding chapters are the subject of continued research and development with ever faster and more realistic algorithms being developed. They are much used in computer games, VR systems and other application where the rendering is performed in real time. For the highest quality display of a scene however, we must turn to a completely different technique for rendering: ray tracing algorithms. These techniques allow “realistic” images to be produced which include shadows, reflection and transparency but such realism is only achieved at vast computational cost. The same underlying data model (surfaces, lines points etc.) is till employed, but the means of converting the model into a scene is radically different.Ray tracing is based upon the fact that a ray of light is “reversible.” Instead of calculating the path of a ray of light from a source, to a surface, through the screen and to the eye (which is effectively what we have been doing) we reverse the light path. For each pixel in an image, we calculate the path of a ray of light from the eye of the observer, through the pixel in question, back into the scene bouncing off surfaces or travelling though transparent (and semi transparent solids) and finally back to the source of illumination. By working out all the factors that could influence the ray of light on its journey, a pixel colour can be deduced. Due to the computational intensity of this approach it is currently usually performed “off-line” with the rendered images being combined afterwards to provide animated sequences.

246

Ray Tracing

14.2

Follow a ray Let’s us follow a ray on its reverse journey from eye to source. Each ray ends its journey at the same known viewpoint having travelled through a single pixel on screen (remember, this process must be repeated for each pixel).

Figure 139. Ray tracing - eye to screen to surface When (by backtracking) we hit a surface, we must work out where the light that goes back in the direction we’ve just come from could have originated. There are several possibilities, each of which may make some contribution to our ray: •a specular ray (for good reflectors) •a refracted ray (for transparent materials) •an illumination ray (directly from a light source) •a diffuse ray (possibly thousands)

247

Ray Tracing

.

incoming ray

illumination ray illumination ray object normal

diffuse rays radiate out in all directions from the surace

reflected ray

refracted ray

Figure 140. Ray tracing - at a surface 14.2.1

The Ray Tree To determine the brightness of each pixel we must calculate all the possible sources of light that contribute to its brightness. Each time a ray encounters a surface it spawns many more (potential) rays. This construction of rays is named a ray tree. Each pixel has it own ray tree. The final brightness of the pixel is the sum of the spawned rays in that tree.

14.3

Types of ray Now we must examine how to treat each of the different kinds of ray:

14.3.1

Normal Rays A normal ray is either a reflected or a refracted ray. When a normal ray strikes an object, if the object is an illumination source, it acquires the brightness of it, otherwise it spawns a reflected ray, a refracted ray and one illumination ray to each other illumination source in the scene.

248

Ray Tracing

14.3.2

Illumination Rays When an illumination ray hits an object, if the object is an illumination source the ray takes the value of the brightness of the source. Otherwise the object is assumed to be dark and the ray is dropped. This is thus the mechanism by which shadow arise.

14.3.3

Diffuse Rays Diffuse rays are a special form of normal rays which are largely responsible for the interactions of surfaces with others nearby. Think about a white sheet of paper held next to a bright coloured surface. Light diffusely scattered from the coloured surface will make the paper appear to take on a tinge of the same colour. A large number of diffuse rays are required for realism. This represents a major performance obstacle to the ray tracing technique.

14.3.4

Computing the brightness As a ray from a source passes through the scene every time it interacts with a surface it loses some brightness either through absorption or through the light being spread over an inclined surface. The effect is an overall loss of brightness. The loss factor, F, can equally well be computed whilst following the ray from the viewer back to the source.

14.3.5

Reducing Computation Ray tracing is a computationally intensive technique, usually executed on high performance workstations. Anything that can be done to reduce the number of calculations will be a welcome improvement. This can be achieved by “pruning” rays from the tree and following them no further. •Any ray that hits darkness can be dropped. •A ray may be traced until its brightness is known •If the loss factor gets to small, then the ray can be pruned. •As a last resort, the depth of the tree can simply be limited.

14.4

Determining Intersections A basic requirement for any ray-tracing algorithm is the ability to detect if and where a ray intersects a surface. The section presents, without proof or derivation, the necessary maths to perform the detection of intersections.

249

Ray Tracing

14.4.1

Outline view volume pla

A ray C centre of projection

su

rf a

screen

ce

ne o

fs

x extent ur

fac

e

B

y extent

z extent

Figure 141. Ray from centre of projection to first intersection with surface The algorithm for determining whether (and where) the path of a ray intersect a surface can be expressed in pseudocode thus: For each pixel on screen, determine the vector equation of a straight line from the centre of projection (see Section 10.4.1) through the pixel and into the view volume. for each surface in the model, find the equation of the plane containing the surface find whether or not line and the plane intersect If they do, find the point of intersection Find whether the surface contains the point

It can be seen straight away that this will take a lot of computational effort if each surface in the model has to be examined for each pixel in the image.

250

Ray Tracing

14.4.2

Find the equation representing the light ray A ray may be considered using a parametric representation of a vector (not a Java Vector) - Figure 142:

(x,y,z) v

(x1,y1,z1)

(x0,y0,z0)

Figure 142. Light rays - parametric vector representation Any point (x,y,z) on the extension of a straight line passing through (x0,y0,z0) and (x1,y1,z1) can be described thus: x = x0 + t (x1 - x0)

(Equation 65)

y = y0 + t (y1 - y0)

(Equation 66)

z = z0 + t (z1 - z0)

(Equation 67)

You might like to think of that as meaning “move from the origin to the point (x0,y0,z0) and then move t times the length of v in the direction of v and you’ll be at point (x,y,z). For the sake of being concise we can define: ∆x = x1 - x0

(Equation 68)

∆y = y1 - y0

(Equation 69)

∆z = z1 - z0

(Equation 70)

x = x0 + t ∆x

(Equation 71)

y = y0 + t ∆y

(Equation 72)

z = z0 + t ∆z

(Equation 73)

then

251

Ray Tracing

14.4.3

Intersection of straight line with a plane Now that we have a convenient means of representing a straight line in 3 dimensions and hence the light ray, let us turn our attention to surfaces. Recall that the equation of any (flat) surface is: Ax + By + Cz + D = 0

(Equation 74)

Substitution and re-arrangement reveal that for any straight line and any surface: Ax 0 + By 0 + Cy 0 + D t = – --------------------------------------------------A∆x + B∆y + C∆z

(Equation 75)

The only case in which a plane and a line will not intersect is when they are parallel. In that circumstance the denominator (bottom) of Equation 75 will be 0. 14.4.4

Determining where a straight line intersects a plane The point of intersection between a line and a plane can be determined by substituting the value of t obtained from Equation 75 back into Equation 71 Equation 73

14.4.5

Determining whether that point is with a given polygon Determining whether that point is within a given polygon is more complex. The approach is to simplify the problem by dropping to 2d instead of 3d. This can be achieved by projecting the polygon onto one of the three planes of the coordinate system. Use the one with the largest corresponding

252

Ray Tracing

coefficient in the plane equation. The projection is achieved by simply ignoring on of the dimensions. In Figure 143, the y coordinates are ignored to project the surface onto the x-z plane y

z

J H’

J’

H’ L

K K’

L’

x

Figure 143. Projecting to 2D The containment of the point can then be solved in 2D. To test whether or not a point is contained in an arbitrary 2D polygon is still not that simple: to infinity (and beyond!)

Figure 144. Points and polygons - Inside or outside? The approach taken is to start at the point being tested, draw a line to infinity and count how many times that line intersects with lines forming the boundary of the polygon. It can be seem from Figure 144 that when the line crosses a boundary an odd number of times, the point must be inside the polygon, if the crossing count is even, then the point must be outside.

253

Ray Tracing

14.4.6

Applying to ray tracing Having determined whether or not a given ray intersect a given surface, the rules outlined in previous sections are used to determine what action to take. Where a ray does intersect a surface, it will be necessary to recursively trace onward any spawned reflected and refracted rays. To determine their direction, the same techniques used in determining reflections for shading algorithms can be employed - see Section 12.2.7 for details

254

Ray Tracing

Chapter review - 14 In this chapter you should have learned: •What ray tracing is and why it is a useful technique •The fundamentals of the technique including:

- the different types of ray: reflected, refracted, diffuse, etc. - the spawning of further rays when a ray encounters a surface leading to a ray tree - that the properties of a light ray are affected by the surfaces it encounters - that sources of illumination are the eventual destination of reverse traced rays •How to trace the path of a ray and the calculation of intersections

between ray and surface.

255

Ray Tracing

Exercises - 14 14.1

Set up the code necessary to calculate the intersection between a ray (represented as either a Line3d or a Vector3d) and a Surface.

14.2

Implement a simple ray tracer in which each ray is traced back only as far as the first surface it encounters.

14.3

Enhance this to spawn the other rays as described in the chapter.

256

Fractals

Chapter 15 Fractals

15.1

Introduction Fractal geometry is based upon the idea of “self similarity” - simple patterns that repeat themselves on smaller and smaller scales to produce complex patterns. The reasons they are of interest to computer graphics developers are two fold: •Many patterns that occur in nature seem to be fractal •The mathematical description of fractals allows very complex pat-

terns to be described very succinctly This means that fractal techniques offer a very efficient way of generating realistic “natural” looking textures and objects in computer rendered scenes. Clouds, coastlines, mountain and trees have all been successfully synthesized using fractals.

15.2

Self similarity The ideas of self similarity are best illustrated by first looking at some two-dimensional examples. We can then look at how these are easily extended to the 3d case. In general, any fractal starts with a base shape and is then modified by repeated recursive application of a generator rule. We illustrate this by reference to the Koch curve:

257

Fractals

15.2.1

The Koch Curve The Koch curve is a geometrical pattern that is generated by the recursive application of a simple rule: Start with a base shape - in this case a simple straight line

Figure 145. Koch curve - base shape Apply the following rule: Take the central third of the line and replace it with a triangle.

Figure 146. Koch curve - first application of generator Now apply this to all of the segments of the resulting line.

Figure 147. Koch curve - second application of generator

258

Fractals

After many repeated application of the rule, an artificial “coastline” is produced. One interesting property of the Koch curve, but common to many fractal patterns, is that the length of the line would ultimately become infinite.

Figure 148. Koch curve -many applications The paintComponent() method of the Gcanvas class that generates the Koch curve is shown in Example 57

Example 57. Generating the Koch curve

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

public void koch (Point2d a, Point2d b, Graphics graf){ Point2d a2b = b.minus(a); Point2d c = a.plus(a2b.scalarMultiply(.333333f)); Point2d e = a.plus(a2b.scalarMultiply(.666666f)); Point2d h = c.vec(e);

Point2d d = c.plus(h.scalarMultiply(.707f)).plus(e.minus(c).scalarMultiply(0.5f)); System.out.println("h: " + h + " d: " + d); if (h.length()<1) { graf.drawLine(Math.round(a.x), Math.round(a.y), Math.round(c.x), Math.round(c.y)); //graf.setColor(Color.red); graf.drawLine(Math.round(c.x), Math.round(c.y), Math.round(d.x), Math.round(d.y)); //graf.setColor(Color.blue);

259

Fractals

19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37

15.2.2

graf.drawLine(Math.round(d.x), Math.round(d.y), Math.round(e.x), Math.round(e.y)); //graf.setColor(Color.black); graf.drawLine(Math.round(e.x), Math.round(e.y), Math.round(b.x), Math.round(b.y)); } else { koch(a,c,graf); koch(c,d,graf); koch(d,e,graf); koch(e,b,graf); } }//koch

public void paintComponent(Graphics g){ Dimension d; d=getSize(); koch(new Point2d(0,100),new Point2d(d.width,100),g); }//paintComponent

The Sierpinski Gasket A second example of the use of base shapes and generator rules is the Sierpinski Gasket: Start with a shaded triangle as the base shape and repeatedly remove the middle of the remaining triangle.

Figure 149. The Sierpinski Gasket

260

Fractals

Example 58. Generating the Sierpinski Gasket

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49

//---------------------------------------------------------------// // // File : Gcanvas.java // // //----------------------------------------------------------------

import java.awt.*; import javax.swing.*; class Gcanvas extends JPanel {

Point2d start; Point2d end;

public void sierpinski (Point2d a, Point2d b, Point2d c, Graphics graf){

int int

[] x = new int [3]; [] y = new int [3];

Point2d d = a.plus(b.minus(a).scalarMultiply(.5f)); Point2d e = b.plus(c.minus(b).scalarMultiply(.5f)); Point2d f = a.plus(c.minus(a).scalarMultiply(.5f));

x[0]=Math.round(a.x); y[0]=Math.round(a.y); x[1]=Math.round(b.x); y[1]=Math.round(b.y); x[2]=Math.round(c.x); y[2]=Math.round(c.y); graf.setColor(Color.blue); graf.fillPolygon(x,y,3);

261

Fractals

50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82

x[0]=Math.round(d.x); y[0]=Math.round(d.y); x[1]=Math.round(e.x); y[1]=Math.round(e.y); x[2]=Math.round(f.x); y[2]=Math.round(f.y); graf.setColor(Color.white); graf.fillPolygon(x,y,3);

if (a.minus(b).length()>20) { sierpinski(a,d,f,graf); sierpinski(f,e,c,graf); sierpinski(d,e,b,graf); } }//sierpinski

public void paintComponent(Graphics g){ Dimension d; d=getSize(); sierpinski(new Point2d(0,d.height), new Point2d(d.width/ 2,0), new Point2d(d.width,d.height), g); }//paintComponent

}//Gcanvas

262

Fractals

15.3

The Mandelbrot Set A more complex and more famous example of fractal geometry is the Mandelbrot Set - a segment of which forms the cover illustration of this book.

Figure 150. The Mandelbrot set This bizarre mathematical object is based upon a mathematical relationship 2

z⇐z +c

(Equation 76)

where z and c are both complex numbers. i.e. assign z the value z2 + c Each pixel in the image represents a point in the imaginary plane. The value (i.e colour) of each pixel in turn is then calculated as follows. Let c represent that point e.g. if the coordinates of the point are (1,2) then let c = 1 + 2i. Let z =0 + 0i

263

Fractals

Count the number of times that Equation 76 can be applied before the value of z begins to diverge - i.e. before either the real or imaginary part grows > (say) 2. Assign a colour to the pixel depending on that count. The code in Example 59 makes the algorithm clear.

Example 59. Calculating the mandelbrot set

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

class Complex {

1 2

import java.awt.*; import javax.swing.*;

public float r = 0; public float i = 0;

Complex (){ }//constructor

Complex (float real, float imaginary){ r=real; i=imaginary; }//constructor

public Complex add(Complex in){ Complex result = new Complex(); result.r = r + in.r; result.i = i + in.i; return result; }//add

public Complex mult(Complex in){ Complex result = new Complex(); result.r = (r * in.r) - (i * in.i); result.i = (i * in.r) + (r * in.i); return result; }//add

public float mod(){ return (float)(Math.sqrt((r*r) + (i*i))); } }// complex

264

Fractals

3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58

//---------------------------------------------------------------// // // File : Gcanvas.java // // //----------------------------------------------------------------

class Gcanvas extends JPanel {

float sf=0.01f; float x_offset= -2.5f; float y_offset= -2; Dimension d;

public int mandel(Complex in ){ Complex orig = in; int i; for (i=0; i<200; i++) { in = in.mult(in).add(orig); if (in.mod() > 2) { break; } }// for i return(i); }

public Color mandel2shade(int in) { int red = (in*1) % 255; int green = (in*1) % 255; int blue = (in*1) % 255; Color result = new Color(red,green,blue); return result; };

public void paintComponent(Graphics g){ int x,y; d = getSize(); for (x=0; x
265

Fractals

59 60 61 62 63 64 65

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40

}// for y }//for x }//paintComponent

}//end of -- Gcanvas ----------------------------------------------

//---------------------------------------------------------------// // // File : Gapp.java // // //----------------------------------------------------------------

import java.awt.*; import java.awt.event.*; import javax.swing.*;

public class Gapp extends JFrame

{

public Gapp(){ setBackground(Color.white); }// constructor

public void initComponents() throws Exception { setLocation(new java.awt.Point(0, 30)); setSize(new java.awt.Dimension(400, 400)); setTitle("Graphics Application"); getContentPane().add(new Gcanvas());

addWindowListener(new java.awt.event.WindowAdapter() { public void windowClosing(java.awt.event.WindowEvent e) {

41 42 43 44

thisWindowClosing(e); } });

266

Fractals

45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78

}//end - initComponents

void thisWindowClosing(java.awt.event.WindowEvent e){ // Close the window when the close box is clicked setVisible(false); dispose(); System.exit(0); }//end - thisWindowClosing

static public void main(String[] args) { // Main entry point try { Gapp myGapp = new Gapp(); myGapp.initComponents(); myGapp.setVisible(true); } catch (Exception e) { e.printStackTrace(); } }//end main

}//end of class -- Gapp -------------------------------------------

Utilising fractal geometry Whilst the above examples are good introductions to the world of fractal, their use in generating “realistic” natural objects is limited. The following three sections however show how the same techniques can be used to generate synthetic versions of natural objects (although we’ll stick to 2d for clarity): mountains and trees. 15.3.1

Mountains Once again we shall use a base shape and a generator rule to create a “profile” of a mountain range. The base shape shall be a straight line (as in the Koch curve example) and the generator rule can be expressed thus: Deflect the mid-point of the line up or down by a random amount limited by a cur-

267

Fractals

rent scale. Various effect can be obtained by changing the manner in which the scale varies as construction proceeds. If successive generation of the profile are overlaid, the evolution of the mountain range can be seen

1st generation 2nd generation 3rd generation

Figure 151. Cyber-tectonics - grow your own mountain range In the following implementation (Example 60), the limiting scale is made to vary with a power of the depth of recursion. If that power (known as the “Jag index”) is varied, different styles of profile result - Figure 152.

Example 60. Fractal mountain profile generator

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

public void mountain (Point2d a, Point2d b, float scale, graf){

Graphics

Dimension d; d=getSize();

float ratio, jagIndex; // // //

jagIndex=1 normal jaggedness jagIndex<1 - very jaggy jagIndex>1 smoother

jagIndex=1.5f; ratio = 2-jagIndex; Point2d c = new Point2d(a.x + ((b.x-a.x)/2),(float)(((a.y+b.y)/ 2) + (Math.random()-0.5)*scale));

if (b.x-a.x>5) {

268

Fractals

22 23 24 25 26 27 28 29 30 31

mountain(a,c, scale*ratio, graf); mountain(c,b, scale*ratio, graf); } else { graf.setColor(Color.blue); graf.drawLine(Math.round(a.x),Math.round(a.y), Math.round(c.x),Math.round(c.y)); graf.drawLine(Math.round(c.x),Math.round(c.y), Math.round(b.x),Math.round(b.y)); } }//mountain

Jag index = 1.2

Jag index= 1.5

Jag index = 1.8

Figure 152. Examples of fractal mountain profiles Several generations are required to produce realistic results, but a limit to the number of generations is needed in order to stop the algorithm. That limit is sensibly reached when the limit of deflection is of the order of one pixel. 15.3.2

Trees etc. Trees share a common fractal structure - a trunk which subdivides in branches which sub-divide into more branches which......

269

Fractals

Once again, our base shape is a straight line. The generator rule is something like, take the top x% of the line and split it into n branches, degrees between each branch. Figure 153 elucidates. θ n=2 x=60%

Figure 153. Generations of a fractal tree The algorithm can be made more complex in several ways: •By varying those parameters, different styles of tree can be

obtained. •By allowing those parameters to vary randomly within limits over the generations some realistic branching patterns can be obtained.

270

Fractals

•By varying the limits over generations some really quite convinc-

ing trees can be generated

Figure 154. Artificial trees

271

Fractals

Of course by pushing some of these variables beyond sensible limits, some extremely unrealistic results can also be obtained.

Figure 155. Teenage mutant ninja spruce

15.4

Fractals and 3D All of the examples of fractals have been given in 2d versions so as not to obscure the functioning of the underlying generating mechanism with the “machinery” of 3D graphics. The basic approach in using fractal techniques in 3D is to generate the objects in the scene using the appropriate fractal techniques and then render the scene using any of the previously covered techniques.

272

Fractals

Chapter review - 15 In this chapter, we have covered: •the basis of fractal geometry and the principle of self similarity •The use of a base shape and generator rule to create self-similar

patterns •The occurrence in nature of fractal patterns •The utilisation of fractals to simulate naturally occurring objects.

273

Fractals

Exercises - 15 15.1

Develop a fractal tree generator.

15.2

Build a 3D fractal landscape generator. Base it upon a mesh and deform the points of the mesh up and down according to some rule.

Closing Remarks

274

Chapter 16 Closing Remarks

In this book I have tried to present computer graphics from the bottom up. If “the bottom” is pixels and memory mapping then I certainly can’t reached “the top” in15 chapters. There are many further topics in CG, but armed with the knowledge in this book, the reader should be able to pick their way through the maze of alternative algorithms and more advanced techniques. Research and development in computer graphics remains as strong as ever, partly driven by the computer games fraternity seeking ever more speed and realism, partly by the academic world with its conferences (SIGGRAPH), journals and other academic paraphernalia and partly by the big budget movie industry. Remember that the examples given in the book are only one way to implement the underlying algorithms. In many cases, I have deliberately avoided the most efficient way of doing something in order to make clear the underlying concepts. Indeed, it can be argued that Java isn’t the most sensible language in which to implement a graphics system, yet given the widespread use of Java as teaching language there is a clear advantage to continuing to use the same vehicle to get convey the concepts of CG. As a final piece of advice I would suggest that no-one (myself included) really understand a concept or a technique in CG without having tried to implement it. None of it is as hard as it first looks. Have a go. Impress yourself.

275

Appendices

Appendices

Vector and Matrix algebra

276

Appendix A Vector and Matrix algebra

A.1

Introduction Computer graphics is concerned with displaying images of 'objects'. The 'objects' are represented in the computer as sets of points lines and attributes (e.g. colour, texture etc.). To display images or views of the objects requires the points and lines to be manipulated by rotation, translation, scaling and projection. These are all geometrical transformations, which are most conveniently represented using vector and matrix algebra. For computer graphics, only a very limited amount of mathematics is essential and it is all contained in this introduction. The emphasis here is on simple explanation and demonstration and no attempt is made to give a serious mathematical introduction. In other words this is a 'get you by' guide to the required mathematics and if you want to take any of the topics further consult one of the books listed in Appendix B - “Bibliography” .

A.2

Co-ordinates and Vectors The most convenient way to represent the position of a point in space is in terms of its perpendicular distance from three axes set at right angles to each other. Fig.1 shows the point P with co-ordinate values x=2, y=4 and z=3.

277

Vector and Matrix algebra

If you imagine that the X and Y axes are in the horizontal plane then the positive Z direction is vertically upwards. This is known as a right handed co-ordinate system. In all but one case points and lines in computer graphics are represented in a right handed co-ordinate system.

z

P

3

4 y

2 x

A left handed co-ordinate system would have the Z axis pointed vertically down. The co-ordinates of the point P are normally written (2,4,3) or in general (x,y,z).

Figure A1. right hand coordinate system

A vector is a line with a specific direction in space. We can regard the point P as the head of a vector from the origin O. In this case the vector p is also written as p = (2,4,3) The values 2,4 and 3 are the components of the vector. It is usual to represent vectors with bold type in texts or as underline letters in hand-written work. A line has a length, which is easily seen from Pythagoras's theorem to be given by : P =

2

2

2

lx + ly + lz

The direction of the vector in space is usually expressed in terms by the cosines (direction cosines) of the three angles it makes with the X,Y and Z axes. x y z cos θ x = ------ cos θ y = ------ cos θ x = -----P P P A special vector, which we often need to refer to, is the unit vector whose length is equal to one. The components of the unit vector are simply the direction cosines. Unit vectors along the three axes have a special significance and are usually designated i (X) j (Y) and k (Z). Two vectors p and q can be added to produce a third vector s by simply adding the components:

278

Vector and Matrix algebra

Graphically the sum is illustrated by 'completing the triangle' as shown in Fig.2. Subtraction of vectors is defined in an exactly analogous way. Multiplication is a little more complicated because there are three type of vector multiplication. All three will be required in the course. The simplest form is:

Q

S

P

Figure A2. Summing of vectors

A.2.1

Scalar multiplication: Each component of the vector p is multiplied by the same constant scalar (real number) value λ. The effect of this is to keep the direction of the vector constant but to change its length by λ λ ( p x, p y, p z ) = ( λp x, λp y, λp z )

Scalar multiplication is useful in expressing the equation of a straight line in vector form. If r is a vector from the origin to any point on the line and u is a unit vector along the line then any point q on the line can be written:

U 3U R O

Q = r + λu

Figure A3. Parametric equations with vectors Figure 3 shows an example for which λ = 3. This is one form of a parametric equation of a straight line with parameter λ. In this context all this means is that all points of the straight line can be reached by variation in the value of the parameter λ.

A.2.2

The Scalar product of two vectors : Two vectors p and q can be multiplied together in the following way to produce a real number value. p.q = px.qx + py.qy + pz.qz

279

Vector and Matrix algebra

The real significance of this definition is that geometrically this represents the projection of one vector onto the other or put in another way it allows us to say what the angle q is between the vectors.

P

θ Q

p.q = |p|.|q|. cos θ Figure A4. No proof of this relationship is offered here but you can find it in the books listed in Appendix B - “Bibliography” .

A.2.3

Example If p is the vector (3,6,9) and q is the vector (-2,3,1). What is the sum of these two vectors and the angle between p and q ? The sum of p and q is simply the vector (1,9,10) The length of p = |p| is given by :

p =

2

2

2

3 +6 +9 =

126 = 11.225

The length of q = |q| is similarly:

q =

2

2

2

–2 + 3 + 1 =

14 = 3.742

The scalar product of p.q is then: (3 x -2) + (6 x 3) + (9 x 1) = 21

p⋅q 21 21 1 cos θ = ---------------- = ---------------------------------- = ------ = --2 p ⋅ q 11.225 ⋅ 3.742 42

280

Vector and Matrix algebra

The third type of vector multiplication we will leave until we have looked at some basic properties of matrices.

A.3

Matrices In one sense matrices are simply rectangular arrays of mathematical objects which can be manipulated according to a set of rules. In the simplest case (the only one we are concerned with) the objects are ordinary real numbers. The significance of objects in the array depends upon the user. Let us take a look at a simple example of a matrix:

 2.0 9.1 0.0     0.5 – 2.9 – 1.0 

This is an array of 2 rows and 3 columns. The dimension of the array is 2 x 3. Each number in the array is known as an element of the array. We can refer to each element by its position in the array. Thus the element 9.1 is in the first row and the second column and is element 1,2. When we wish to refer to a complete matrix without listing its elements we will use a bold capital letter (e.g. A). A vector you may have noticed is simply an ordered list of three numbers and, if we wish, we can regard it as either a matrix of dimension 1 x 3 (row vector) or of dimension 3 x 1 (column vector). In this course all vectors will be column vectors. In the computer graphics course we are only concerned with matrices of dimensions 3 x 3 and 4 x 4. These are examples of square matrices. We can give three useful definitions in terms of the following general 3 x 3 matrix:  a 11 a 12 a 13     a 21 a 22 a 23     a 31 a 32 a 33  The line of elements from a11 to a33 is known as the leading diagonal. If the following is true for all elements not on the leading diagonal aij = aji then the matrix is symmetric. If on the other hand for all elements not on the leading diagonal aij = -aji then the matrix is antisymmetric. The matrices representing rotation in computer graphics are examples of antisymmetric matrices.

A.4

Arithmetic with matrices With a fairly obvious restriction the operations of addition, subtraction and multiplication are easy to define for matrices.

281

Vector and Matrix algebra

Addition and subtraction for the matrices A and B are defined as adding or subtracting the corresponding elements of the matrices. The obvious restriction is that the matrices must be of the same dimension.

Example 61. Matrix addition:

 1 2 3  2 5 8   3 7 11    +  =    4 5 6   3 1 –9   7 6 –3 

With multiplication the definition is a little more complicated. The 'ij'th element of the product of A and B is formed by considering the 'i'th row of A as a row vector and the 'j'th column of B as a column vector and then setting the 'ij'th element to the scalar product of the two vectors. For this to work the column dimension of A must be the same as the row dimension of B. A numerical example should make this clear:

Figure 156. Matrix Multiplication    1 2 3  1 4   23 17  A•B =   2 5  =    4 5 6    55 44   6 1 The element 1,2 is calculated from:

( 1, 2, 3 ) • ( 4, 5, 1 ) = 1.4 + 2.5 + 3.1 = 17

Thus a 2 x 3 matrix can be multiplied by a 3 x 2 matrix to give a 2 x 2 matrix. Clearly the product B.A is not defined in this case. With square matrices both A.B and B.A will be defined but you must be aware that in most cases A•B≠B•A

282

Vector and Matrix algebra

This is very clearly illustrated in computer graphics where (in 2-dimensions) translation (T) and rotation (R) are both represented by 3 X 3 matrices and a rotation followed by a translation (TR) does not produce the same result as a translation followed by a rotation (RT) Fig. 5.

1 - original position 2 - translate 4 units along x 3 - rotate 45°

1 - original position 2 - rotation 45° 3 - translation 4 units along x

Example 62. Translating a point using a matrix

The vector v = (1,2,1) is multiplied by the translation matrix T below. Calculate the translated vector v'  1 0 10    T =  0 1 10     0 0 1  This represents a translation by 10 units in both the x and y directions.  1 0 10   1   11       T v =  0 1 10   2  =  12        0 0 1  1   1  Thus the vector v' is (11,12,1)

A.5

Determinants Just as forming the scalar product is a rule for calculating a simple number from a vector which is useful for various applications a similar approach can be adopted for matrices. In simple terms the determinant of a matrix is a rule for evaluating the elements of the matrix. This is very much an over-simplification because determinants are mathematical entities in their own right with their own set of properties and algebra. However the only fact about

283

Vector and Matrix algebra

determinants that you need to know for this course is the rule for evaluating a 3 x 3 determinant. The determinant of the matrix A is usually written as |A| or det(A). The rule is best explained by considering the simpler case of a 2 x 2 determinant as follows: If A is the matrix:  a11 a12     a21 a22 

then: det ( A ) =

a11 a12 a21 a22

= ( a11.a22 – a12.a21 )

For a 3 x 3 determinant the definition is an extension of the above: If A =  a 11 a 12 a 13     a 21 a 22 a 23     a 31 a 32 a 33  then A =

a 11 a 12 a 13 a 21 a 22 a 23 a 31 a 32 a 33

      = a 11.  a 22 a 23  – a 12  a 21 a 23  + a 13  a 21 a 22   a 32 a 33   a 31 a 33   a 31 a 32 

= a11(a22a33-a23a32)-a12(a21a33-a23a31)+a13(a21a32-a22a31) At first this looks a very obscure definition but if you look at it in terms of the position of the elements you should see the symmetry in the definition. In practice evaluation is quite easy:

A.6

The vector product of two vectors We now return to the subject of multiplying two vectors. The vector product of two vectors generates a third vector (hence the name) by the following rule:

284

Vector and Matrix algebra

The vector product of two vectors a and b, with an angle θ between them, is the vector whose length is |a|.|b|.sin(θ) and whose direction is perpendicular to both a and b. This is usually written:

a × b = a ⋅ b sin ( θ ) • n

where n is a unit normal vector perpendicular to the plane containing a and b. There are, of course, two directions perpendicular to the plane containing a and b and n is defined to be in the direction a normal screw or bolt would travel if it is turned from a to b. The important implication of this is that: a x b = -b x a

z n

b x

y

a

If i,j,k are unit vectors along the three axes X,Y,Z then you should be able to convince yourself of the following: i x i = j x j = k x k =0 i x j = k = -j x i j x k = i = -k x j k x i = j = -i x k

285

Vector and Matrix algebra

A.7

The Distributive law In a similar manner to the scalar product the distributive law holds for the vector product in the form: a x (b + c) = a x b + a x c We can use the distributive law to deduce a useful expression for evaluating the components of the cross product of the vectors a (a1,a2,a3) and b (b1,b2,b3) as follows:

This may be written in the form of a determinant: Find a unit vector perpendicular to the two vectors (4,-2,3) and (5,1,-4) A vector which is perpendicular to the two vectors is the cross product of the two vectors. Using the formulae above for the components of a cross product if (4,-2,3)X(5,1,-4) = (x,y,z) then x = ([-2].[-4] - [3].[1]) = 5

y = ([3].[5] - [4].[-4]) = 31

z = ([4].[1] - [-2].[5]) = 14 Thus the vector (5,31,14) is perpendicular to the two vectors it is not, however a unit vector but we can generate a unit vector by dividing each component by the length of the vector The length of the vector is given by:

Thus a unit vector (rounded to three figures) is (0.145,0.902,0.407)

A.8

Parametric Equations Almost everyone is familiar with the equation of a straight line in two dimensions: y = mx + c in which the points of the line are given by pairs of values (x,y), m is the line gradient and c the intercept of the line with the Y axis. The equation represents an infinitely long line but in computer graphics we are most often

286

Vector and Matrix algebra

interested in lines of a finite length. An alternative form of the equation for a straight line is often more convenient. Suppose we wish to represent a line from (x1,y1) to (x2,y2) then consider the following two equations : x = x1 + (x2 - x1).t y = y1 + (y2 - y1).t If t = 0 then x = x1 and y = y1 the first point on the line. Similarly if t = 1 then x = x2 and y = y2 the last point on the line. In fact all points along the line segment between (x1,y1) and (x2,y2) can be generated by choosing a value for t between 0 and 1. The equations are the parametric form of the equation of a straight line with parameter t. The equation of a line segment in three dimensions can be formed by adding a third equation: z = z1 + (z2 - z1).t Example 63. Example

The equation of a circle in two dimensions centred on the origin is usually represented by x2 + y2 = R2 As an alternative we can represent this in parametric form as x = R.Cos(θ) y = R.Sin(θ) Here the parameter θ is the angle with respect to the x axis and ranges from 0 degrees to 360 degrees (2π radians).

287

Bibliography

Appendix B Bibliography

Leen Ammeraal, Computer Graphics for Java Programmers, 1998, Wiley ISBN 0-471-98142-7 Foley, van Dam, Feiner, Hughes and Philips, Introduction to Computer Graphics , Addison-Wesley 1993, ISBN 0-20160-921-5 Glenn W Rowe, Computer Graphics with Java -Palgrave, 0-333-92097-X Patricia Egerton and William Hall, Computer Graphics - Mathematical First Steps, 1998, Prentice-Hall, ISBN 0-13-599572-8 James Gleick, Chaos, Abacus Nina Hall, The New Scientist Guide to Chaos, Penguin, 0-14-014571-0

Computer Graphics Using Java - MAFIADOC.COM

between the centre of the circle and the middle of pixels S and T . Whichever .... Data Structures and. Drawing. 5.1 Introduction. This course in computer graphics is centred on understanding the algo- rithms used to generate 'on screen' graphics ...... (fps), movie 24 fps whereas simple cartoon animation may use 12-15 fps.

3MB Sizes 32 Downloads 248 Views

Recommend Documents

computer graphics -
Sutherland – Hodgeman Polygon clipping Algorithm. 7. Three dimensional transformations - Translation, Rotation, Scaling. 8. Composite 3D transformations. 9.

COMPUTER GRAPHICS Set No:
Information Technology, Computer Science and Systems Engineering ) · Time: 3 hours · Max.Marks:80 ... Outline the z-buffer algorithm. List the advantages and ...

Using Imported Graphics in LATEX2ε
Dec 15, 1997 - Section 6 describes graphics-conversion programs while Section 13 describes how to use non-eps .... Since TEX cannot read non-ascii files and cannot spawn other programs, LATEX cannot read the ...... The 0.05 term is due to the differe

pdf-1410\design-graphics-1-with-computer-graphics ...
pdf-1410\design-graphics-1-with-computer-graphics-by-james-h-earle.pdf. pdf-1410\design-graphics-1-with-computer-graphics-by-james-h-earle.pdf. Open.

Using Imported Graphics in LATEX2ε
Dec 15, 1997 - Since neither LATEX nor dvips has any built-in decompression or graphics- conversion capabilities, that software must be provided by the user. c© Copyright 1995-97 by Keith ... There are several advantages to placing graphics in figur

Making lip-syncing simple - Computer Graphics Home
Flash CS3 or later. Making lip-syncing simple. Learn how to speak easy by going through the simple steps of lip- syncing using Flash CS3 ... illustrator Tom's.

PDF Interactive Computer Graphics
Online PDF Interactive Computer Graphics: A Top-Down Approach with ... with WebGL (7th Edition), All Ebook Interactive Computer Graphics: A Top-Down .... of engaging 3D material early in the course so students immediately begin to create ...