Evaluating Java for Game Development

By Jacob Marner, B.Sc. Department of Computer Science University of Copenhagen, Denmark (jacob@marner.dk) March 4th, 2002

1

PROLOGUE ...................................................................................................................................4 1.1 1.2 1.3 1.4 1.5 CONVENTIONS .........................................................................................................................4 KNOWLEDGE REQUIRED OF THE READER..................................................................................4 THE STRUCTURE OF THE REPORT ..............................................................................................4 ACKNOWLEDGEMENTS.............................................................................................................5 HOME PAGE..............................................................................................................................5

2 3

INTRODUCTION ..........................................................................................................................6 GAME DEVELOPMENT..............................................................................................................7 3.1 3.2 3.3 A DEFINITION OF COMPUTER GAMES ........................................................................................7 GAME PLATFORMS ...................................................................................................................7 THE BUSINESS OF GAME DEVELOPMENT ...................................................................................8

4

INTRODUCING JAVA ............................................................................................................... 12 4.1 4.2 4.3 4.4 4.5 4.6 THE TERM JAVA .....................................................................................................................12 OVERVIEW .............................................................................................................................12 THE JAVA PROGRAMMING LANGUAGE ...................................................................................12 THE JAVA PLATFORM .............................................................................................................13 THE JAVA STANDARD LIBRARY ..............................................................................................15 THE JAVA DEVELOPMENT KIT ................................................................................................15

5

JAVA/C++ LANGUAGE COMPARISON.................................................................................17 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9 5.10 5.11 5.12 5.13 5.14 5.15 5.16 5.17 5.18 5.19 5.20 5.21 5.22 5.23 APPLICATION VS. SYSTEM PROGRAMMING .............................................................................17 OBJECT ORIENTATION ............................................................................................................17 PORTABILITY .........................................................................................................................17 DYNAMIC DATA ALLOCATION AND GARBAGE COLLECTION ...................................................19 ARRAY BOUNDS CHECKING ....................................................................................................20 REFERENCES AND POINTERS ..................................................................................................20 TYPE CASTING........................................................................................................................21 THE STANDARD LIBRARY .......................................................................................................21 PROTOTYPES AND HEADERS ...................................................................................................22 DOCUMENTATION GENERATION .............................................................................................22 LANGUAGE SIMPLICITY ..........................................................................................................23 OPERATOR OVERLOADING .....................................................................................................24 UNSIGNED INTEGRAL DATA TYPES .........................................................................................24 CONSTANT PARAMETER TYPES...............................................................................................24 PACKAGES AND NAMESPACES ................................................................................................25 MULTIPLE CLASS INHERITANCE .............................................................................................25 TYPE DEFINITIONS AND ENUMERATIONS................................................................................25 PREPROCESSING .....................................................................................................................26 TEMPLATES AND GENERICS ....................................................................................................27 DYNAMIC CLASS LOADING .....................................................................................................28 REFLECTION ...........................................................................................................................28 DEBUGGING AND IDES ..........................................................................................................28 SUMMERY ..............................................................................................................................30

6

JAVA PERFORMANCE .............................................................................................................32 6.1 6.2 6.3 6.4 6.5 6.6 THE ORIGIN OF THE JAVA MYTH .............................................................................................32 WHEN IS JAVA SLOW? ............................................................................................................33 WHEN IS JAVA FAST? .............................................................................................................34 WHY IS THIS JAVA PROGRAM SLOW?......................................................................................41 MEMORY FOOTPRINT .............................................................................................................42 SELECTING THE HEAP SIZE .....................................................................................................43

2

6.7 6.8 6.9 6.10 6.11 7

THE PERFORMANCE OF JNI ....................................................................................................44 TEMPLATES / GENERICS .........................................................................................................44 STRINGS .................................................................................................................................45 TWEAKING AND READABILITY ...............................................................................................45 CONCLUSION..........................................................................................................................46

BENCHMARKS ...........................................................................................................................47 7.1 7.2 7.3 7.4 7.5 7.6 7.7 7.8 7.9 7.10 7.11 7.12 INTRODUCTION ......................................................................................................................47 CHOICE OF COMPILER/VIRTUAL MACHINES ............................................................................47 THE SYSTEM SETUP ................................................................................................................49 THE BENCHMARK APPLICATIONS ...........................................................................................49 BENCHMARKING BOTH TWEAKED AND UNTWEAKED APPLICATIONS ......................................50 WARM-UP PERIODS ................................................................................................................50 GENERATION OF TEST DATA ...................................................................................................51 A PARTICLE SIMULATOR ........................................................................................................52 AN OBJECT-ORIENTED VERSION OF LIFE ................................................................................57 A 3D DEMO ............................................................................................................................62 REVIEWS OF THIRD PARTY BENCHMARKS...............................................................................68 CONCLUSIONS AND THOUGHTS ..............................................................................................72

8

EVALUATING JAVA FOR A GAME PROJECT....................................................................74 8.1 8.2 8.3 8.4 8.5 8.6 8.7 8.8 8.9 WHY USE JAVA? ....................................................................................................................74 CONSIDERING THE RISKS ........................................................................................................75 OPENGL, DIRECTX AND OTHER LIBRARIES ...........................................................................79 JAVA3D .................................................................................................................................79 DIFFERENT WAYS JAVA CAN BE USED IN GAMES ....................................................................81 COMMERCIAL GAMES USING JAVA .........................................................................................81 PORTABILITY .........................................................................................................................83 THE PERFORMANCE OF JAVA IN GAMES..................................................................................84 SHOULD I USE JAVA FOR MY NEXT GAME PROJECT?...............................................................85

9

CONCLUSION .............................................................................................................................87

APPENDIX A: REFERENCES ...........................................................................................................88 APPENDIX B: UNTWEAKED PARTICLES SOURCE CODE ......................................................92 APPENDIX C: TWEAKED PARTICLES SOURCE CODE..........................................................118 APPENDIX D: UNTWEAKED LIFE SOURCE CODE .................................................................145 APPENDIX E: TWEAKED LIFE SOURCE CODE .......................................................................154 APPENDIX F: UNTWEAKED 3D DEMO.......................................................................................163 APPENDIX G: TWEAKED 3D DEMO ............................................................................................226 APPENDIX H: EDITED THIRD PARTY BENCHMARK ............................................................305 APPENDIX I: BENCHMARK RESULTS........................................................................................308

3

1 Prologue
This report is registered as report 01-11-11 at the Department of Computer Science, the University of Copenhagen, Denmark. The report was written by Jacob Marner with Associate Professor Jyrki Katajainen as supervisor. It was turned in on March 4th, 2002. The purpose of this project is to examine whether the use of Java for games is advantageous compared to the traditional language of choice, C++. This is not an easy question to answer, and as we will see later the answer will depend on several project specific issues. The target group of the report is professional game programmers with little or no knowledge of Java, who wonder whether Java would be beneficial in their future projects. The report generally assumes that the reader is skeptical about Java.

1.1 Conventions
When words or phrases written in courier are used in the report, they always refer to pieces of C++ code, Java code, or file names. The first time a new special term is introduced and explained in the report, the term will be written in bold. References will be denoted with square brackets.

1.2 Knowledge required of the reader
The reader of this report is expected to have at least a background in Computer Science equaling a Bachelor’s degree (B.Sc.) and to be experienced in practical programming besides that taught at university undergraduate courses. Furthermore, the reader should have a strong knowledge of C++ programming. Any prior knowledge of Java is not needed since the needed information this will be given in the report. Although it is generally expected that the reader knows about game development in general terms, this is not traditional B.Sc. knowledge. This will therefore be briefly introduced early in the report. It is recommended that readers without knowledge of C++ read something about it first. One good C++ primer is [Lippman]. [Kernighan] and [Stroustrup] are recommended as language references.

1.3 The structure of the report
Chapter 2 will introduce the main question that this report aims to answer.

4

Next, chapter 3 will attempt to define games and give an overview of the game market today and narrow down the focus of this report. Chapter 4 will briefly introduce Java to the uninitiated. This will not be a programming tutorial. Rather it will introduce the various concepts and technologies that are important to understand the world of Java. In chapter 5, the Java and C++ languages will be compared and the pros and cons of each will be discussed. This will be followed up in chapter 6 with a discussion of the performance of Java compared to that of C++. Chapter 7 runs some benchmarks measuring the relative performance of Java vs. C++. Chapter 8 is perhaps the most important chapter in the report. Here it is considered how Java can be used for game developed and it is discussed whether doing so is a good idea. Finally, in chapter 9 the conclusions of the report will be summarized in a short concise form. If you do not have time to real all of the report, I recommend reading only chapters 8 and 9. Following the conclusion is the reference list (Appendix A), the benchmark source code (Appendix B - Appendix H) and the benchmark results (Appendix I).

1.4 Acknowledgements
I would like to thank the people at the forums at http://www.javagaming.org. Without the many – sometimes heated – discussions I have had at those forums I would not have been able to write this report.

1.5 Home page
The home page of this report is: http://www.rolemaker.dk/articles/evaljava/ On this page you can find the report itself, the source code for the benchmarks, the original Excel spreadsheet with the benchmark results, and any errata to the report.

5

2 Introduction
In the last couple of years the use of Java has become very widespread in the ITindustry. It is used mainly for Internet software, but it is also popular for regular applications and in embedded systems. In fact, according to [Galli] over half of all U.S. developers use Java and this share is projected to rise with additional ten percent during 2002. This rise in the interest in Java is mainly due to the fact that developers have a higher productivity when using Java compared to traditional languages such as C or C++, especially when doing cross-platform or Internet development. The use of Java is not widespread among developers of computer games sold in retail. It is therefore natural to ask why this is so, and ask whether game developers can benefit from using Java. If it is true that Java can reduce production cost, it might be advantageous to use it Java for game development. This is the question that I aim at answering in this report. I will compare Java with C++, consider the performance of Java, clarify some common myths, run some benchmarks, and finally discuss various ways Java can be used in game development. It would also have been interesting to evaluate the use of the Microsoft .NET platform (and C#) for game development, but doing such an evaluation is a large topic in itself and is therefore outside the scope of this report.

6

(Primarily for Microsoft Windows. Computer game development is normally not viewed as constituting a computer science field by itself.3 Game development In this chapter I will attempt to give an overview of what game development is and then narrow down what segments of the game industry that will be considered in this report. an application of computer science that makes use of many different fields. possibly distributed. and artificial intelligence. compiler design. software engineering. HCI. a game typically is an interactive real-time simulation. 3. To mention the most common: • Personal computers. All you need to know is that the remainder of this report only will consider games targeted at personal computers and game consoles. A model is maintained of a virtual world and this model is updated and visualized in real-time according to the dynamics of the model and the interaction with the user or users. (Primarily the Nintendo Gameboy Color and the Nintendo Gameboy Advance) 7 . If you are a professional game developer then you can skip this chapter. software design.1 A definition of computer games A computer game is a piece of interactive software created with the primary purpose of entertaining the user. Games for personal computers usually support networking) • Dedicated Game Consoles that connect to a regular television. but some are also being made for MacOS and Linux. • Web-based games. including. however. 3. Seen from a programming viewpoint. networks and distributed systems. but not limited to graphics. and the Microsoft Xbox) • Arcade machines. Computer game development is. operating systems. the Nintendo GameCube. • Dedicated hand held consoles with an embedded screen.2 Game platforms Games are getting more and more popular each year and therefore appear on a multitude of platforms. the Nintendo 64. This definition is not clear-cut but is still useful. algorithms. audio. (This includes machines such a the Sony Playstation 1 and 2. since it characterizes the prototypical computer game.

Apple Macs have a small market share and Linux almost none. woman and middle aged and older men. Of these. the Xbox. and this is likely soon to become a dominating player. Sega has taken the consequence of this trend and has stopped producing consoles. I have been unable to determine the impact these games has on the distribution of revenue. • Other hand held devices. In several years in a row the market growth was in excess of 20% a year [Veronis] although last year this growth was somewhat lower. that the revenue of PC and console games worldwide in 2000 including the sales revenue of the actual consoles was higher than the box office sales in the U. however. (Is usually placed at bars and cafes) • Slot machines. that same year. from a business point of view. the dedicated consoles represent approximately 2/3 of the revenue and the personal computers the last 1/3.S. DVD.• Interactive Television. Microsoft has recently (November 2001) released its first console. This low revenue for Linux users has probably to do with that Linux users are accustomed to free software. Since the personal computers and dedicated consoles dominate the game industry scene I will focus on only these platforms for the remainder of the report. There has been a rising in the popularity of web based games especially among groups that do not usually play games. (This includes devices such as the Palm Pilot) • Entertainment kiosks. It is true. 8 . Furthermore. then personal computers and dedicated consoles (including the hand held ones) represent the vast majority of the revenue involved in games. (Devices provided by television cable networks to enhance the capabilities of the television) • Cellular phones. (Is usually placed at bars as a replacement for the older one-armed bandits) If we ignore the games in which you play for money. but I believe that it is insignificant compared to the personal computer and console market. 3. Sega has a small market share but this has been diminishing for some time. [Veronis] On the personal computer scene. It is often said that the computer game market is larger than the film market. The dedicated game console market is currently dominated by the devices by Sony and Nintendo. Linux is mostly popular as an operating system for professionals – not home users. (Games hosted at a television broadcaster and controlled via a regular tone telephone) • Set-top boxes. This is not entirely true. is interesting because it is a market that is in rapid growth. on VHS.3 The business of game development The game market. Intel Windows PCs dominates the market completely when it comes to games. To say that the gaming market is bigger than the film market one should also include the world wide sales of films in theaters.

Some analytics claim that it is a myth that shelf space is limited [DFC 2] but I think those analytics forget to take into account that many stores. If this goal is not achieved then sales will be significantly lower [Wardell00]. We will consider both high and low profile game in the discussions in this report. the actual televisions and video players and so forth. The high profile games must be technically advanced. so it is no longer clear if even the statement above holds any more. It becomes very essential to become one of those few games that retailers put in the front shelf and that magazines use many pages to cover. such as Blockbuster. issues such as marketing become vital to the success of a game. Alternatively. Unfortunately this is not entirely the case. have no more than 10 different games on sale at any given time and that many stores have shelf space with varying levels of prominence. High profile and low profile games often have different goals in development. In practice. In 2001 the U.1 The business model of PC games The typical business model for a PC game is similar to that known from the book or music industry in that there is a strong division between developers and publishers. [DFC 1] Because of this. cannot compete with this under any circumstances. To get the attention of retailers and the media. We will call these games low profile games. The developer does the actual software development while the publisher markets the game. focus has traditionally been on graphics. because the amount of developers has grown even more than the market. games may be anywhere on the scale between these two extremes. each game attempts to become the one with the highest profile. Low profile games. Another solution for low-profile games is to 9 . There has been a recent trend for developers to sell their games directly on the Internet but this is generally only a viable solution for low profile games. In order to compete for prominent shelf space. It is here presented as if games either are fully high profile or fully low profile games. but because of the lower budget they may still be profitable. This is more than the growth the same year in the game industry. Nonetheless. on the other hand. some developers focus on niche groups of users and decrease cost equivalently or even just focus on producing a high quantity of low-budget games rather than a few high-budget games. Each of these games will have significantly lower sales than their high profile counterparts. movie industry has seen a growth in excess of 30%.3. This is the kind of games we will call high profile games. This currently increases the cost of developing these games to the range of 2-3 millions US$ combined with a fairly high risk of failure. 3. When doing the comparison that way.S. The result is that the competition has become harder. so here the primary goal is to keep costs down.merchandising. produces the actual physical media and packages and distributes it. games are far behind the movie industry [Wardell01]. Performance is therefore vital. it sounds like there is a lot of money to be earned in games at the moment.

make the games web-based. Because of these royalties. This move has led some people to believe that Sony will allow more people to access information about the Playstation 2 and to develop games for it. 3. keyboard and mouse) for the Sony Playstation 2 in Japan only. This licensing scheme also means that there are significantly fewer very low profile games available for consoles in comparison with what is available for PCs. who works with Sony on the Sony Java initiative. In return they require royalties for each game sold for the platform. this does not mean that two thirds of developer profits are gained on this market. but not that it will be possible to develop full Playstation 2 games.3. Side note: Linux on the Sony Playstation 2 In 2001 Sony released a limited number of Linux kits (including a hard disk. but with some important differences. games are played just as much by adults. in which case they are often free to play and financed via advertisements. This also allows the manufacturers to control the content of games being published on their platform and thereby to avoid. I have been told by Jeff Kesselman of Sun Microsystems. Linux on the Playstation 2 uses a significant amount of memory (the Playstation 2 has only 32 MB RAM) making it impossible to run competitive games. the console manufacturers require that only developers with a development license may develop software for their specific console.3. for instance. If the development team has previously created success titles then a demo might not be necessary. 3. Since financing games usually is risky for the publisher. Vital information about the software interfaces of the consoles is kept secret and is only given to licensed developers. Second. First. They have furthermore announced a general release in Japan. This is true to the extent that it is possible to develop standard Linux applications for the Playstation 2. Usually a developer presents a demo to the publisher and then the publisher finances the development of the rest of the game. the software SDK for the system is still kept secret. the US. So although two thirds of the revenue is on the console market. and UK in June 2002. adult content. although the 10 . Today. the developer profit of selling console games is less than for PC games. Second.3 Who plays games? In contrary to popular belief computer games are no longer played primarily by children and teenagers. the console manufacturers sell the consoles with a loss to increase their market share. Becoming a licensed developer is not an easy task. that Sony never will release the specifics of their system as this would be they would loose control of the platform and the lucrative royalties that follows. the publisher earns the majority of the resulting income. to ensure that they can get their royalties.2 The business model of console games On the console market the business model is almost the same. First.

• The PC market has no specific focus but there is a tendency to focus on male young adults. • Sony and Sega focuses on male teenagers and male young adults (up to 35 years old).main bulk still is below 35 years old and male. The target is pre-teenagers and to some extent also teenagers. This is probably because many of these very low profile games are financed via advertisements and therefore free to play for the end-user. [DFC 2] The various platforms have chosen to focus on different target groups for their products: • Nintendo focuses on family games. games that children can play with their parent. Worthy of note is that web-based low profile games on the PC are popular especially among woman and middle aged adults (over 30 years old). because they are much simpler to use than a PC. 11 . i.e. This makes this more accessible to groups that would not normally spend money on games. Consoles are generally attractive to players that are either young or inexperienced with computers. This change has occurred both because computer games has increased much in popularity in the last decade and because those game players that used to be children have grown up and still play games.

These byte code files can then be executed on any platform that complies with the Java platform.3 The Java programming language The Java programming language is an imperative object oriented programming. enabling them to understand the remainder of the report.sun. • The Java Development Kit (JDK) Although these concepts are related it is important to distinguish between them since they no necessarily need to be used together. but rather attempt to give an overview of the concepts involved. This is usually done by the implementation of a Java virtual machine which emulates the Java platform on another platform. Its syntax is very similar to that of C++. [Coulouris] 4. • The Java standard library. Each of these will be introduced in the following sections. 4. For instance. For more detailed information about Java please go to the official Java web site at http://java. the Java virtual machine protects the underlying machine from illegal instructions and erroneous memory access.2 Overview Traditional Java applications are applications that are written in the Java programming language and then compiled into byte code form – called Java byte code.com/. 4.4 Introducing Java In this chapter Java will be briefly introduced to readers with no knowledge of it. with the use of a Java virtual machine for Linux. In effect C++ programmers can usually read and learn Java source code without much trouble. one is able to execute Java byte code on Linux. This is important when using mobile code.1 The term Java The word Java is trademarked Sun Microsystems and are used to cover several related concepts: • The Java programming language. The major benefit of using a virtual machine over compiling directly to machine code is that the application in compiled form can be completely portable. This chapter will not teach how to program Java. Furthermore. 12 . • The Java platform or implementations thereof.

A detailed discussion of the differences between the C++ and Java programming languages can be found in chapter 5. This approach is similar to that used by traditional languages such as C or FORTRAN and is therefore not appropriate for mobile code. but not so high that it like ML or Python rightly can be called a high level programming language. • Virtual machines with Hotspot compilation. 13 . The details of Java can be found in the Java language report [Gosling00]. The Java run-time environment usual consists of two parts: • A Java virtual machine (JVM) • An implementation of the Java standard library (see section 4. implementing a virtual machine as a simple interpreter is not the only approach available. but is still useful for games that are sold in retail.5).The language is of a slightly higher abstraction level than C++. Fortunately. Other approaches are: • Virtual machines with Just-in-time (JIT) compilers that compile the byte code to platform specific machine code at run-time just before it is needed.4. We will discuss Java performance in more depth in chapter 6. • Embedded implementations. 4. 4. This is similar to JIT compilers except that it focuses on compiling the hotspots in the code and supports adaptive optimization. Hotspot virtual machines and static compilers are currently the most efficient approaches for executing Java on PCs and will these therefore be the main focus on this report.1 Virtual machines Traditionally Java applications have run very slowly because the virtual machines were simple interpreters that read and executed byte code instructions one at the time. A specific implementation of the Java platform is called a Java run-time environment (JRE). • Static compilation that at design time compiles Java byte code to an executable file that is machine specific. This approach is currently not well suited for games on the PC and consoles and will not be considered further. Hotspot compilers used more commonly than static compilers so most focus will be on those. Hotspot compilers are often said to contain a JIT compiler. Static compilers cannot be called virtual machines in the traditional sense. Due to improvements in virtual machine technology this is no longer the case.4 The Java platform The Java platform is a standard that tells how Java byte code should behave. but was included were because they replace virtual machines in order to run Java applications. This approach uses specialized hardware to build a machine that complies with the Java platform.

Such embedded applications are called Java applets. Java run-time environments can also be embedded in applications. Due to the high portability of Java byte code the programs can (usually) run without change no matter what machine the browser runs on. This extends the standard edition with several features well suited for large scale server side enterprise applications. each targeting different uses: • The standard edition (J2SE) This edition is intended for personal computers and is usual the edition referred to when people discuss the Java platform. 4. It is common knowledge that Java is useful for web-based games so applets will not be discussed in much detail in this report. The micro edition is divided into a set of profiles that defines the characteristics of the edition on a given class of devices. This edition is a scaled down version of Java for use in systems that does not have the capacity to run the standard edition. This is the same for the enterprise edition but significantly less for the micro edition.4. • The micro edition (J2ME). The micro edition is actually not a single implementation. most noticeably World Wide Web browsers. If two devices share the same profile then software will be portable between them.4.2 Platform editions The Java platform comes in several variants. • The enterprise edition (J2EE). For more details about Java virtual machines please see the Java virtual machine specification. since different devices have different requirements and constraints. These approaches will be discussed in more detail in chapter 6.3 Java Applets Besides being used for development of regular applications. 14 . Related to the micro edition is a whole suite of technologies such as PersonalJava and EmbeddedJava. Implementations of the standard edition often require 5-10 MB of memory just to run. These browsers can execute (client side) applications embedded in web pages. These applications are downloaded dynamically and automatically from the web page. For instance. [Lindholm] 4. These are mainly intended for embedded systems so small that they cannot even use the micro edition. Several such profiles exist.When this report refers to virtual machines in the text. this does not usually include the static compilers. This edition will be the edition discussed in this report unless otherwise noted. set-top boxes and digital assistants each have their own profile describing a certain subset of Java to be used on those devices.

4.5 The Java standard library
The Java standard library is a large collection of functionality that can be used many common tasks, such as math, strings, common data structures, I/O, time/date handling, audio, multithreading, networking, serialization, graphical user interfaces, and cryptography. The API that interfaces the standard library from the Java programming language is usually termed the Java core API. One important part of the core API is the Java Native Interface (JNI). This allows Java applications to interact with C and C-like C++ code allowing them to extend to functionality of the standard library with system specific functionality. When people refer to 100% pure Java applications, they usually refer to Java applications that make use of the Java core API only. Extra Java libraries are allowed, but these may not use JNI. Besides the core API, Sun provides a number of optional packages that extends the core API in various ways. An optional package is simply a programming library. These libraries often make use of JNI to access system specific features and must therefore be specially ported to each platform. Since the number of optional packages are high the availability of these libraries are usually low except on those platforms for which Sun provides implementations; i.e. MS Windows, Sun Solaris and to some extent also Linux. It is important to realize that optional packages are nothing but normal programming libraries with the special property that are officially supported by Sun. The optional packages provide much new functionality. Examples are 3D graphics, emailing, advanced imaging, and access to computer communication ports. Of special interest to this report is Java3D, the optional package that provides 3D graphics.

4.6 The Java development kit
The Java development kit (JDK) is the name of the official Sun toolkit used to produce Java applications. It primarily consists of a compiler that compiles Java language source code to Java byte code and the full documentation of the Java core API. Many other vendors provide their own version of the JDK, but all of them refer to their level of Java support by referring to the Sun version number. The latest version of the JDK is version 1.4.0 which was released February 14th 2002 only few weeks before this report was published. The official Java virtual machine implementations follow the same version numbers. It is important to use latest versions of Java since older versions of the virtual machines are significantly slower than the new ones.

15

When working with Java it is important to know the major versions:

JRE/JDK version 1.0 1.1 1.2

Description A simple interpreter. Introduced JIT compilation. Changed to use the Java 2 platform. Matured JIT compilation. Introduced Hotspot compilation. Matured Hotspot compilation.

Release Date January 23, 1996 February 18, 1997 December 8, 1998

1.3 1.4

May 8, 2000 February 14, 2002

For more information about the time line of Java technology, see [Sun]. Note that although Java 1.0 was released in 1996, Java is normally said to have been released in 1995, because of the beta versions that went before it. With each version the speed of Java applications has improved significantly. When making speed comparisons in this report we will only use version 1.4 since this represents the current state of Java most precisely. It has been the source of some confusion that JDK versions 1.2 and later use the Java 2 platform. At least in theory these cover two different things. The first signifies the JDK while the other signifies the platform. Unfortunately, there has been made changes or additions to the platform in each JDK version so this does not hold completely. However, with JDK 1.2 major changes was made to the platform which is probably why it name was changed to the Java 2 platform. Since JDK 1.3, Sun has provided two different virtual machines, the client virtual machine and the server virtual machine. The server virtual machine performs more optimizations than the client virtual machine but takes longer to warm up (see section 6.3.2).

16

5 Java/C++ language comparison
The syntax of the Java and C++ languages look very much alike, but this similarity is only partial. The similarity is made on purpose to make the transition to Java easier for C and C++ programmers. There are a number of important differences between the languages and their development environments that will be reviewed in this chapter. Many of these differences contribute to difference in programmer productivity between the two languages. How much the increase is in Java will be discussed in chapter 8. Not all differences between the two languages are covered here. The review assumes the reader has a good knowledge of C++. This chapter will generally not discuss the consequences the languages differences have on performance. That will be part of the general performance discussion in chapter 6.

5.1 Application vs. system programming
Java is an application programming language, while languages such as C or C++ are system programming languages. In essence this means that Java does not allow direct access to the underlying hardware. The consequence is that Java is not suitable for writing low-level software such as hardware drivers or operating system kernels. It is intended for application programming only. Java will therefore never completely replace C++. It should also therefore not be a surprise that Java virtual machines themselves are written in C++.

5.2 Object orientation
In C++ the use of object oriented code is optional. Users can ignore objects and write C-like applications without objects. In Java the use of object oriented programming is mandatory. All variables and functions must be defined as part of a class. On one hand this reduces flexibility during programming but on the other hand it helps to ensure code modularity.

5.3 Portability
Java source code is almost 100% portable between different compilers, and Java byte code is almost 100% portable between different platforms and different virtual machines. It is not exactly "write once, run anywhere" but it is the closest you get with any

17

programming language or environment that I am aware of and it is in fact very close to achieving it. If you are developing for multiple platforms this high level of portability is an important benefit. There are several language features that help to ensure portability: • The sizes of Java data types are identical on all implementations. This is not the case with C++. • Java uses only Unicode for characters. This removes code page problems and also eases adding internationalization support. • The standard library is big enough to actually allow advanced programs being written without the use of external libraries that is not written in 100% pure Java. If a program accesses native code (i.e. C or C++ code) then this native code must of course be manually ported to each target platform. The result of this high portability is that applications very often can be compiled on multiple Java compilers and run using multiple Java virtual machines without changing anything. As long at the underlying computer does not change, switching Java compilers and virtual machine works even if the application uses JNI. The effect of this is that Java developers often try out their application on several virtual machines and based on the result chooses the one that gives the highest performance. In contrast, compiled C++ programs are not portable. C++ source code is somewhat portable, assuming that they do not use features outside the limited standard libraries, and that the compilers do not differ too much. C++ portability often sounds better in theory than it is in practice. The C ANSI library is today fully adopted on all major C++ compilers but there are still several compilers that do not conform to the rest of the C++ standard library (including the STL containers) or even supports the full C++ language standard – or they support it with some variations. Furthermore, common functionality, such a multi-threading or graphical user interfaces, is not standardized with C++, making applications using such features much less portable. The portability of Java relies on that assumption that the used virtual machines and standard libraries are fully standard complying. Fortunately, this is the case with most virtual machines. The Sun implementation is in practice the defining one. Third parties then license the Sun source code as the base of their own Java virtual machines. This makes the functionality of virtual machines very identical and portability among virtual machines a non-issue. The only exception to this is the static native compilers, since these works much differently from regular virtual machines. In practice this means that there can be some portability problems with some of those. I therefore recommend that you never rely on being able to use static compilation for your final release.

18

5.4 Dynamic data allocation and garbage collection
In C++ all dynamic data must be allocated and deallocated explicitly. If the programmer forgets to deallocate the data then there is a memory leak that might cause the system eventually to run out of memory. If the programmer on the other hand deallocates data too early and he then accesses it, the behavior will be undefined. These problems can be solved to some extent with proper tools such as Compuware/Numega Boundschecker. In fact, using such a tool is highly recommended for any C++ developer. However, even in the presence of good tools, handling these problems is no trivial task. Java does not support explicit deallocation of data. Instead, all data that is no longer referenced is automatically garbage collected preventing virtually all pointer allocation/deallocation related problems. (The single exception is discussed in section 5.4.2) The performance consequences of using garbage collection will be discussed in chapter 6.

5.4.1

Garbage collection in C++

It can be argued that garbage collection is also possible in C++. This is correct but only to some extent. If you build your own garbage collection routine in C++ then you can generally take one of two basic approaches: • Allow the use of regular pointers and perform the garbage collection by using some kind of mark and sweep algorithm scanning the memory for data that looks like pointers. This type of C++ garbage collector may fail to recognize unused memory if you use the C++ memory in unexpected ways and may therefore not always work. One serious problem with this approach is that the garbage collector may not move objects at run-time, which would invalidate pointers. This significantly reduces the performance of the garbage collector since it makes it impossible to make generational garbage collection (see section 6.3.3). One free library using this approach is Garbage Collector by Hans J. Boehm. Using Garbage Collector is not easy, because it is quite complicated to set up. • Encapsulate pointer operations and add a reference count to each object. When the reference count becomes zero, the object is garbage collected. This is the clean way of doing it, but is significantly slower than what is possible when using a mark and sweep algorithm. This algorithm fails if you use normal pointers instead of the encapsulating objects. A simple implementation of this approach is described in [Photon]. This is also the approach used within the Microsoft Component Object Model (COM). For both types of garbage collection the system may fail, because third party libraries not always may observe the required restrictions. In Java these problems are nonexistent,

19

because all libraries support garbage collection automatically, and because garbage collection is guaranteed to work in all cases.

5.4.2

Memory leaks in Java

In one sense of the word Java does have memory leaks but it does not have it in the traditional sense. In C++ you have to remember to call delete on every object that you have created with new. If not, the object will never be deleted and you have a memory leak. In Java you just have to be sure to delete all references to an object in order for it to be deallocation. This can happen if you have some references to objects that you use throughout the application that contain some references to objects that no longer is in use. The situation is much less severe in Java (compared to C++) because if you delete a reference to an object then the references that the object itself contains are also automatically deleted. This does not happen in C++, where it is vital that every single object is deleted exactly once. To summarize, Java can in one sense be said to contain memory leaks, but they are much less severe and much rarer in practice than those in C++. Java profiling tools, such as the popular OptimizeIt can detect this kind memory leak.

5.5 Array bounds checking
C++ programs can access data outside the bounds of arrays. This may cause very hardto-find bugs, but may also be used by the resourceful programmer to do specialized access to the system memory. The use of tools such a NuMega Boundschecker can at the expense of extra overhead usually detect when this happens. In Java an exception is always thrown when an array is accessed out of bounds, meaning that such errors always are caught immediately.

5.6 References and pointers
In C++, the programmer has to handle pointers to memory addresses explicitly. Since pointers may point to illegal memory addresses or even to addresses containing data of other types, many hard-to-find errors might occur. The use of pointers often causes confusion to beginning and intermediate C++ programmers, because of the many issues involved. For instance, in C++ all variables can be passed both by-value, by-reference, and as a combination caused by passing an address as by-value. Java solves this problem by replacing pointers with references. A reference is internally the same as pointer in C++, but with added security:

20

7 Type casting In C++. If the type cast is a down cast (i.• A reference cannot point to deallocated memory. type casting is always controlled. the user can freely change the type of a pointer without any run-time check to verify that the type cast is valid. Primitive types include data that can be stored in a single memory address. since an object is deallocated automatically if no references point to it any more. One prominent example is string support.8 The standard library C++ comes with a decent standard library. No type check is needed when up casting. Because the C++ standard library includes a variation of the C standard library many idiosyncrasies occur within the library. As discussed in section 5. do this behind the scenes) but only on the heap. Java does not support templates and must rely on common base classes in generic containers.e. however. networking and GUIs is not supported. including the STL (Standard template library) and a library extremely similar to the C standard library. 5. However. • A reference cannot point to an object of an incorrect type. this is also the source of many errors. The C standard library does have support for strings. The C++ standard library solves this by providing a dedicated string class. Java distinguishes between primitive types and object types. Object types are always accessed via a reference and are always passed by-reference. The result of this is that the amount of explicit type casts needed in Java usually is far higher in that in equivalent C++ programs. This is useful in some special cases where data stored in one way is to be viewed in another. so this should be made as easy as possible. To achieve this. References are themselves primitive types. Java does not support explicit allocation of objects on the stack – this is only possible for primitive types. This consists of several components. In Java. to a derived class) then a type check occurs at run-time to detect whether the type cast was legal. The manipulation of strings is important to many applications. These are always passed by-value and are stored on the stack. These libraries are general limited in functionality. 21 . but they are very awkward to use and require manual allocation of data each time. but unfortunately most 3rd party libraries do not support these. One can only type cast object references that are related by inheritance.19. 5. Essential features such a multithreading. • Memory leaks in the traditional sense cannot occur. Object types cannot be stored explicitly on the stack (virtual machines may.

5. 22 . and advanced imaging. Java did not have to be compatible with a much older language and was able to do the library design more cleanly from the start. Furthermore. It automatically extracts class names. 3D graphics with hardware acceleration.thereby very often forcing the programmer to fall back on the traditional and awkward C strings. Java contains (unlike C++) a multiple pass parser/compiler so there is no need to use prototypes. graphics. It also makes it easier to maintain the documentation since it is stored just beside the source code it documents. e-mail. In practice maintaining these headers are tedious to do. database access. networking. This increases productivity significantly. modem support. multimedia. CORBA. sound. math. data/time manipulation. The user can insert special comment tags that are used to generate the actual documentation content. In Java. remote method invocation (a kind of RPC support). URLs. the programmer gets (this includes features from the optional packages): strings. Such a tool reads the source code and turns it into html documentation.22. parameter types and names and generates pages with hyperlinks to make the documentation easy to navigate. header files or even to consider ordering of variables and methods other than for readability reasons.10 Documentation generation In C++. server side programming. Such documentation generation tools also exist for C++.9 Prototypes and headers In C++ a programmer often has to place prototypes for functions or global variables in header files and place the definition in regular source code files. which thereby often have better interoperability than C++ libraries. Java supplies a lot of advanced functionality in its standard libraries relieving the programmer from much work. this rather advanced set of features is used as the common denominator of many 3rd party libraries. speech. documentation is usually written separately from the code and is usually hard to keep up to date. and not to be underestimated. various standard container types and algorithms. To mention just a some of them. cryptography. it has been convention from the beginning to use document generation tools. We will discuss development environments more in section 5. but they are not widely used. multithreading. a GUI system. 5. These tags also allow Java development environment to provide instant documentation to the user about symbols in the source code. With Java there is a strong advantage here because users can read the API documentation the same way no matter who made the library. All in all these issues cause the C++ standard library to be quite awkward to use. Unlike C++. a visual component architecture. authentication. communication port support.

Java. Much of this complexity comes from two sources: • The legacy from C. 23 . Personally. This is compensated by the fact that Java is a simpler language and this makes the job of making a good optimizing compiler easier. Especially the postfix version of the -. The main advantage of this is that it is easier to learn and that less time is used during programming to think about language details. • The need for initializers in constructors for efficiency. int j& = i. • The wish to allow the programmer to access to low level constructs in order to fine tune performance and access hardware directly. For instance. the public interface) of a class. on the other hand. Below is a list of some of the strange details in C++ that programmers must take care of: • Strange syntax when using pointers to functions as arguments to functions. even after having programmed C++ for more than 7 years. Reference variables are included because this allows functions to pass parameters by-reference instead of by-value.and ++ operators contain a strange syntax as they need a dummy int parameter. but that is has less complexity. (which in fact causes aliasing).: X& X::operator--(X&. • The need for reference variables. • You need to declare static member data twice. Not that it has less features. Originally C++ was designed to be fully backwards compatible with C. • The syntax for defining operator overloading is quite hard to remember. This has been done in order to be more portable but also to increase programmer productivity by bringing programming to a higher abstraction level. Look in any C book and notice that the authors usually comment on the messiness of this syntax. The price is. that Java cannot be finely tuned for performance on a low-level. has no legacy and has chosen to remove many low-level details. but the programmer must rely on the optimizing compilers to do the job.int). e.11 Language simplicity Java is a simpler language than C++. Java just requires them to be in the beginning of the function block. of course. Although C++ has evolved on its own and is no longer fully backwards compatible the legacy remains. if I have a static member variable then it must be declared in the header file and defined in a source file. A lot of programmers get confused when they see statements such as: int i. • Why use semicolons after classes and structs? Java does not need them.5.g. C++ is a big and complex language.e. • The need to declare private members in the header (i. I regularly learn new details about the language.

Java does not. • The need to use inline and register keywords. etc. 5. say vectors. 24 . linking options. I am not sure I agree. extern "C". Clever use of operator overloading of relevant classes. The compiler verifies that the constant declarations are respected. • The need to avoid that certain header files are included more than once and the problems with cyclic dependencies between header files.12 Operator overloading C++ supports the use of operator overloading.e. by the use of the const keyword. If a reference or a pointer to a constant is passed then the value of the object cannot be changed.• The need for the friend keyword. a method can be declared constant. 5. Probably due to the many hard-to-find bugs related to the mixing of unsigned and signed types Java has chosen to avoid unsigned types and therefore only support signed data types.13 Unsigned integral data types In C++ all the primitive integral data types comes in both signed and unsigned variants. since it can detect many errors at run-time where some code my mistake change something that it should not. The exception is the char data type which is a 16-bit unsigned integral (recall that Java uses Unicode for characters) and which do not exist in a signed version. • The importance of knowing that the order of execution of the initializers in a constructor depends on the ordering of the fields in the struct or class. __pascal. far pointers and other concepts such as dll imports/exports resources. 5.14 Constant parameter types C++ supports the passing of constant parameters and the declaration of constants. This feature is a powerful one. The rationale for this Java limitation is that operator overloading often reduces code readability. • That you have to know about all kinds of details such as declarations __cdecl. • Various #pragma declarations. namespace) access modifiers this would not have been needed. Furthermore. meaning that it is not allowed to change the this object. This also means that all manipulations made on the object must be constant. If C++ supported package (i. Personally. can significantly increase readability.

i. The purpose of this is usually either to: 25 .Similarly Java supports a final keyword. This means that a final reference to an object still allows the object to change. This scheme completely solves name clashing issues in Java. but be placed in the dk/marner/ subdirectory of the Java class root. A related problem is the clashing of source file names. library writers often prefix function names with some identifier to make them stand out from the rest. or a branch under it. no method definitions and no fields.17 Type definitions and Enumerations In C and C++ you can give an already defined type a new name. Since multiple class inheritance in some cases is useful this is a clear advantage of C++.e.marner. final seems to be identical with const but are not. Java only supports single class inheritance and multiple interface inheritance. 5. But this does not solve the problem completely as authors sometimes use duplicate identifiers. This is a clear disadvantage of Java. meaning that code for the package dk. 5.16 Multiple class inheritance C++ supports multiple class inheritance. I own the domain marner. Almost needless to say the final mechanism is much less useful than the C++ const mechanism and detects fewer errors at compile time. but it does not allow the reference to point to another object. An interface in Java is basically a class with no implementation. A final parameter or reference just means that the reference itself is constant. Java solves these problems by providing a standard package scheme. This is a clear advantage of Java. A package is basically the same as a namespace in C++. this code is placed in the package dk. so when I publish code. The source code must also be organized according to this scheme.marner. To circumvent this.dk. For instance. 5.15 Packages and namespaces In C there are generally a lot of problems with name clashing between functions. C++ improves this scheme somewhat by providing namespaces but this does not solve the problem completely. except that packages are organized hierarchically and the naming is based on Internet domain names but with reverse ordering. This can be done either with typedefs or #defines.

one can include extra code in debug builds that is not included in release builds. 5. 26 . Instead of enforcing its own views. the second use is still relevant. among other things. so the programmer does not have to care about it. is still relevant in Java. • Make new names that help the user to understand the purpose of values stored in that type. I disagree with this. which is supposed to be an unsigned 32-bit value no matter the compiler. you either have to define a set of constant integer variables or use the Enum design pattern [Meurrens].• Make the code easier to port to other C/C++ compilers. a language should give this choice to the programmer. because the language already is made to be portable between compilers. This preprocessing step allows. Personally. I do not agree with this rationale and consider this limit a drawback of Java. Because of the high portability of Java code. In Java it is not possible to rename types or to define enumerations. The rationale for not including it in Java is that these name changes often causes more confusion than they are an advantage. This allows the team to get better bug reports from the users and make it easier to perform field debugging.18. • To distinguish between different kinds of builds.1 Conditional compilation Conditional compilation is mostly useful for two purposes: • To make code portable among different platforms. The second use. it is not acceptable to keep debug checks in final releases. “Portable” C/C++ code often makes heavy use of this. However. This is also essentially what enumerations in C and C++ do. but I acknowledge that opinions differ in this matter. In fact Java does not have a preprocessor at all. the use of macros and conditional compilation. although some third party Java preprocessors do exist. 5. Java supports neither. OpenGL defined a type GLuint. The first type of use presented above is not needed in Java. If the code is performance critical. For instance.18 Preprocessing Before C++ source code is passed through the actual C++ compiler it is first passed through a preprocessor. To make an enumeration in Java. however. For instance. the first use is not relevant for Java. The rationale for avoiding conditional compilation is that debug checking never should be removed and that bug checking should remain even when the application is deployed to the end-user.

19 Templates and generics Templates. even when optimizing compilers or templates could do just as well a job. a list of doubles. These can be used to define parameterized macros that in effect work like templates. a list of float.Sun has acknowledged the need to distinguish between debug and release builds and with JDK 1. The disadvantage of this clearly is that the code becomes harder to maintain and will taker longer to write in the first. If code of similar efficiency was to be made in C++ it would either require the use of macros or of a lot of code redundancy to write specialized versions for each function/class. E. etc. This generally leaves only three options for creating polymorphic functions or classes: • The use of a common base class. This is how many generic algorithms were implemented in C++ before the introduction of templates. are a powerful feature in C++ that allows the use of strongly type checked polymorphy in classes/functions while being very efficient.4 support for assertions has been included in Java. also known as parameterized classes/functions. A prototype implementation has already been completed. A problem with this way of implementing polymorphy is it does not apply to primitive types. This has been done for the sake of code readability. Sun is aware of this problem and has planned the support of Generics for a later version of Java [JSR14].g. it is probably true that this feature is much misused in C and C++ in the name of performance. 5. Generics will probably be included with JDK 1. • The use of third party Java preprocessors. Assertions can be removed at run-time when one trusts that the code is bug free although it is still present in the byte code in order to make it easier to do field debugging.18.5 but there is no guarantee of this. definition of macros is not possible in Java. Java supports neither templates nor macros.3 and later they can sometimes be eliminated during optimization. Such down-casts have traditionally been expensive in Java but with JDK 1. Generics are similar to templates in syntax and the main purposes are to: 27 . This solution is quite elegant except for the fact that the user of the polymorphic method has to make a lot of explicit down-casts from the base class to the derived class. Although one is tempted to think that there is cases where the use of macros can be justified.2 Macros Likewise. on would write a list of ints. [Assert] 5. The disadvantage of this is that the code becomes less readable and somewhat messier to debug. • To write specialized versions of the classes. This is the solution used by the Java standard library. So for the sake of code readability and maintenance I agree with this choice in Java and consider it an advantage of Java.

For C++ the absolutely best integrated development environment (IDE) is Microsoft Visual Studio*. 5. This is useful in development environments where the user visually is able to set attributes of objects that the environment previously knew nothing about. Another factor is how good the available tools are for the language. Generics will not allow polymorphy between primitive types and object types. combined with VisualAssist from Whole Tomato Software and NuMega For the purpose of this evaluation I have used Visual Studio 6. The reflection mechanism allows Java programs to query information about unknown classes to learn about what methods and fields they have and then later call it. The C++ RTTI supports similar functionality but in a far more limited way.22 Debugging and IDEs The language itself is not the only factor that determines how productive a programmer is with it.• Eliminate the need for the explicit down-casts that would be required without generics. This functionality is a central part of the Java component architecture. • Ensure consistency of types.NET * 28 . For instance.21 Reflection Dynamic class loading is taken a step further with the Java reflection mechanism. JavaBeans.20 Dynamic class loading A strong feature of the Java language and platform is the support for dynamically loaded classes. I know that it is supported in both Excelsior Jet and gcj. without generics a list container may contain elements of differing types (derived from the generic class Object). This essentially means that code can be added and (with a few tricks) replaced at run-time. For instance. It should be noted that not all Java static compilers support dynamic class loader. 5.0 SP5 since I did not have experience nor access to Visual Studio . 5. To obtain dynamic functionality in C++ one usually makes use of some scripting language. With generics (or templates) it is ensure that the list only contains elements of the specified type. it allows components/classes to be plugged into a program and then made available to the user. C++ code on the other hand is completely static after it has been created.

There are no limitations with the debugging features in Java like there is in C++. there do exist one that has limited refactoring support for C. For code editing this environment is to my knowledge unsurpassed by environments provided by any language on any platform. neither that nor any other C++ environment I know is perfect. This is likely only possible because • Debugging support has been integrated into the Java virtual machine from the beginning. if that even is possible.Boundschecker. They do. • Visually navigate data that are of a type derived from the type being navigated. I have in fact been unable to find any tools at all for C++ that supports refactoring†. Some of the included features are variable. Although the visual debugging tools are advanced they still have problems with in some cases. however. but good candidates are Eclipse (previously known as IBM WebSphere Studio) or IntelliJ IDEA. Combined with the protected environment this helps to detect bugs early in the development process and thereby increase productivity. This is called Xrefactory and is an Emacs plug-in. It should be noted that the advanced editing features of Visual Studio easily can be applied to Java. for debugging. Although no refactoring tools exist for C++. including allowing the user to: • Visually navigate the data in dynamically allocated arrays. • Visually navigate complex templates. These are both excellent development environments but do not provide the editing productivity of Visual Studio. the Java IDEs are very powerful. This environment provides the programmer with an excellent productivity. This environment also supports pluggable compilers which allow it to be used to develop applications for both the PC and consoles. And for debugging. This will probably happen before the visual debugging problems with C++ are fixed. They can only be viewed by viewing specific fields in the array via a watch or by browsing the raw memory. including those in the STL. but this has yet to be done. It is not easy to determine the best Java development environment at the moment. • Java is a simpler language. However. support things not available for C++ environments such as extensive tool support for refactoring [Fowler] which more than compensates for the lack of editing features. function and method tab-completion and visual indication of most syntax errors during editing (including the detection of unknown variables or variables not in scope). † 29 . although they are not far from it.

• There is no need to use header files or prototypes in Java. • Java does not support constant objects or constant object parameters. The advantages of the Java language: • Java source code and byte code is significantly more portable than C++ code. and uses garbage collection instead of explicit deallocation. • Java applications runs in a completely protected environment. • Java does not support multiple inheritance of classes. uses references instead of pointers. The disadvantages of the Java language: • Java does not support templates and Java does not support polymorphy between primitive types and object types. while the best Java IDEs are superior when it comes to debugging and support for refactoring. • The Java syntax is simpler than that in C++ with fewer details including the elimination of many unsigned types. 30 . dangling pointers. which removes all name clashing problems. It is therefore a good guess that we will see more improvements in Java tools over the coming years than in C++ tools. We start by listing those features that is advantages of Java and then list those that are advantages of C++. traditional memory leaks. 5. • The standard library included with Java is superior to that of C / C++. This includes errors such as illegal memory access. type casting. The best C++ IDE is a little better at editing. I think it is an important point that better tools are easier to make when a language is simple. the best IDEs of C++ and Java are of approximately the same quality today.To summarize. access to deallocated memory and more. • Java support dynamic addition and replacement of code at run-time and run-time inspection of such code. We already have evidence of this by the fact that several refactoring tools exist for Java while none yet exist for C++ . • Java does not support typedefs or enumerations.23 Summery The major differences between Java and C++ that affects productivity are summarized below.although the refactoring principles can be applied to both languages. arrays accessed out of bounds. • Java is easier to debug. This prevents virtually all pointer related errors from running unnoticed. but only single inheritance of classes and multiple inheritance of interfaces. • Java supports packages.

Low-level access can still be gained. Many of the differences between the two languages have the result of increasing productivity. 31 . not low-level system development such as development of hardware drivers.• Java does not support operator overloading. • The editing tools C++ are currently a little better than those for Java. Furthermore. the design of C++ could probably not have been much better. with the use of JNI. though. Most of the issues with C++ are due to its legacy from C. Given that C++ at its creation had to be backwards compatible with C. Java is only suited for application development. How much these productivity gains sum up to is discussed in chapter 8.

6 Java Performance In this chapter we will discuss the performance of Java – especially as seen in relation to the performance of C++. I heard the same hyped arguments back then and originally dispelled the use of Java as anything but a web applet language. Chances are that you tried it back then. I did not return to Java until late in 1998. If you are a C++ programmer then you were probably very tired of all those Java evangelists back in 1995 that claimed that Java was superior in every regard and that 100% pure Java was the best thing there was. However. 32 . They even sometimes claimed that it ran practically as fast as C++. all this does not mean that Java is useless for professional gaming purposes. And if you even today do a quick web search on "Java games" you get nothing but small low quality applets that is not very impressing. These games just help to maintain the reputation of Java as being a language that cannot be used for real games. but this chapter is presented first because proper writing and running of benchmarks requires good knowledge of Java performance in general. This will be done both theoretically but also based on both the benchmark results in chapter 7 and my personal experience. This chapter uses the results of chapter 7 a lot. and that any measured differences were insignificant. because of the many promises that was clearly not true. and then dismissed it as a web development toy and decided that the Java evangelists either was liars or fools. 6. it all depends on how you use it. saw how awfully slow it ran. most the hype surrounding Java has since then died out and the compilers and virtual machines has improved significantly in meantime.1 The origin of the Java myth Java has a strong reputation for being a slow language that cannot be seriously considered for real applications. Fortunately. It has not gotten this reputation by coincidence. As we will see in this chapter Java is in fact not as slow as its reputation claims.

It has gotten faster lately but it is still not fast enough. Unless users explicitly install a modern Java plug-in browsers use an old version of the Java technology that is very slow compared to what is possible today.2 When is Java slow? It is in fact true that many Java applications to this day still are very slow.2.1 Problems in the first Java virtual machines As can be seen from the tables comparing the speed of Java and C++. What version one uses are very important for performance. old versions of Java is in fact very slow.1 v1. You may have tried Java applications outside your browser and found them slow too. You may also have run your own tests using Microsoft J++.1 unless the user explicitly installs a Java plug-in. The performance difference between the old versions of Java (see section 4. this is not really a problem for games development.2 Factors slower than C++ (approx.6. since that is one of the slowest versions you can get of Java.6) and C++ is approximately (this is very rough numbers. You have probably experienced this first hand by playing some small games on some web page with your browser. means that Java uses x times more time than C++ to perform the same computation. determined by running a few simple benchmarks that is not documented in this report and will vary wildly depending on the actual application): JDK/JRE version v1. 6. This has mainly to do with the fact that they use old versions of Java. It is no secret that Microsoft is no supporter of Java – especially since the announcement and release of C#. Judging Java based on that is not fair. Fortunately. This is probably because it was a GUI application and the GUI system in Java is noticeable slower than native Windows or XWindows.) 20-40 10-20 1-15 That Java is a factor of x slower that C++. 33 . As can be seen from the table above. This explains why web applets run so slow.0 v1. Even modern browsers such a Netscape 6 and Microsoft Internet Explorer 6 still only comes with a Java virtual machine at version 1. the speed of Java applications has increased significantly with each major version. since games usually do not make heavy use of the builtin Java Windows system.

However. This is 34 . Needless to say. • Allow the compiled machine code to be recompiled at run-time if the involved set of classes changes. Seemingly this should cause Java programs to become as fast as any statically compiled programs. These compiled each class to machine code before the first time it was executed. This is the approach use by Hotspot virtual machines (JDK 1. 6. Two approaches have been used to solve this problem.1 Just-in-Time (JIT) compilation was introduced to the virtual machines. This was not easy in Java because each class in byte code form is completely independent of other classes. According to the conclusions of chapter 7. this was very slow. How fast they are depends on several factors which we will discuss here.5. This is the approach used by the Java static compilers. if the Java program is tweaked (and the C++ program is tweaked too for fairness in comparison) this difference is reduced significantly.Originally Java virtual machines were simple byte code interpreters that executed one command after another.1 and JDK 1. 6. This flexibility is enhanced even more by the possibility of dynamically loaded classes. the number of explicit type casts is generally very high.5 to 4 times slower. Since Java does not have templates.3 When is Java fast? Modern Java applications are sometimes slow and sometimes not. Unfortunately. One very important factor is the amount of tweaking that has been performed on the Java program.2 was compiled to machine code one at the time the consequence was that it was impossible to inline method across class boundaries or even within classes themselves if those methods were not private. The definitions of tweaked and untweaked code are presented in section 7.2 Other issues that slow down Java Other specific problems that traditionally have slowed down applications written in the Java languages are: • All down-casts must be type checked at run-time. a non-tweaked program will be several factors slower than an equivalent C++ program. but must be allocated on the heap. • Objects cannot be stored explicitly on the stack. With JDK 1. In my tests it was 2. this was not the case. • Assume that all classes immediately present are static and compile the classes as was they written in any other statically compiled language. since traditional compiler technology relies heavily on inlining to gain their high performance.3 and later).2.2. This approach will be discussed in more detail in section 6. • At array accesses are checked at run-time to verify that they occur within bounds of the array.3. Since each class in JDK 1. Many highly tweaked programs have in fact been shown to be able to run faster in Java than in C++. causing many more objects to be allocated.

2 to 1.1 The amount of code that needs to be tweaked When a Java programmer has written some code that has been written with readability and maintenance in mind.75) 75% slower – in the worst case. this code is classified as untweaked. As mentioned above I estimate that such programs are 2. For instance assuming the worst case that the tweaked part of the program is 50% slower than C++ and the rest is 4 times slower.3. So naturally this tweaking should only be done in parts of the program that has been shown to be very computational intensive. so the conclusions are not clear. so if the programmer tweaks those 10% the program will run almost as fast as had all of the program been tweaked. but fortunately this not mean one has to tweak all of program code.also the case with one of the benchmarks in chapter 7 (The Tweaked Life application).5 = 1. 6. If such tweaking is done the productivity falls and the main point of using Java in the first place will be lost.) 0. the end result will be that the program is (0. JDK/JRE version v1. most of the execution time is spent in 3D hardware. This is not good. In many programs as little as 10% of the code is run 90% of the time.5) The only major drawback is that it is often quite hard to tweak programs in Java to achieve this performance.2-1.7-4 0. In some cases tweaked Java code is up to three times slower than C++ and in some case it is double the speed of C++ code – but in general the tendency seems to be that it is a little slower. This technique should be augmented with the use of a profiler after the program has been written. 35 .1 * 4 + 0. if the program uses 3D hardware. When designing a program the programmer should try to determine which areas will have the highest throughput and then isolate those parts from the rest of the program so they can be easily tweaked for performance.4 Factors slower than C++ (approx.5 to 4 times slower than untweaked C++ on the average.5-3 (typically 1.5 slower). which means that the performance difference between Java and C++ becomes even less. When people talks about the high productivity programmers have with Java they usually assume this kind of programming. This is the case with the 3D Demo benchmark in section 7.3 v1. Furthermore.9 * 1. as most contemporary games do.10. Judging from the various benchmarks I would say that tweaked Java programs on the average runs 20-50% slower than tweaked C++ program (= a factor of 1. The results vary a lot from application to application.

The optimizations that can be performed on the code can be much more aggressive than the optimizations performed with traditional static compilers. but is planned for later releases. are: • The compiler can. which are unique to Hotspot technology. The first many times some piece of code is run it is run interpreted. This is how the technology got its name. also change.3 and later). [Hotspot] The idea behind this technology is that the application is analyzed at run-time and optimizations are performed based on these results. virtual methods) can be turned non-virtual if they are not derived or never used polymorphically. • Non-final methods (i.4). Escape analysis is not efficient with traditional compiler because they have to be more conservative. and when the system has gathered enough information about the run-time behavior of the application it aggressively compiles the sections of the code that are run most. Of optimizations.2 Hotspot technology One of the major innovations in Java is the development of Hotspot technology (JDK 1. because the compilation can be undone and redone at any time. the Hotspot virtual machine first attempts to in-line as much code as possible. This strategy introduces an initial overhead to the application as it starts up (the socalled warm up period) while it analyzes run-time behavior and runs the optimizing compiler. based on run-time observations about the behavior of the code. a kind of adaptive optimizer technology. For instance. This means that if the general run-time behavior changes then the machine code can. and probably will. thereby improving processor pipe-line utilization. The idea is that it is better to use much time compiling the code that is run often rather than performing inferior compilation to it all as is usually done by JIT compilers. provide accurate hints to the processor about what choice will happen in ifstatements. • Variable escape analysis that allows objects to be stored on the stack instead of on the heap. In general. This optimization is according to [Hutcherson] not yet implemented in the Sun JVMs (JDK 1. Special optimizations for Java are: • Array bounds check elimination.6. To fully 36 . Then the full range of traditional optimization techniques is applied to inlined code.3. • Elimination of checked type casts. an Intel Pentium 2 has and an Intel Pentium 3 has different performance characteristics.e. such as C++ compilers. the hotspots. • Compilation occurs at run-time meaning that it can take into account performance characteristics of the specific architecture it runs on.

3. Analysis has shown that most objects in Java programs are short lived. This also used to be the true. Allocation and deallocation in Java can be done faster than in C++ mainly. short lived objects are virtually free since they do not take time during the scan for live objects. However.2). 6. This could lead us to believe that Java might quickly become faster than C++. Hotspot provides some unique possibilities for optimization that static compilers do not. Finally it should be noted that C++ compilers in principle also can choose to adopt adaptive optimization technology just like Java. however. so making such a compiler for C++ is a more complex task than it is for Java. This is mainly because C++ is a more complex language. because Java does not allow direct access to heap memory. since there are also things that slow Java down compared with C++ (see section 6. When a new object is allocated is it allocated within the nursery. When the nursery is full this area is garbage collected and any surviving objects are moved to the next generation. Allocation can happen basically by returning the current heap pointer and then increasing the value of the heap pointer with the size of the allocated object. This means that the garbage collector at run-time is able to reorganize and fully defragment the heap.utilize the pipelines one must know the precise architecture. Allocation can be done in constant time. only the future will show. Whether this will happen. is where recently allocated objects are stored. This is currently a problem. something which is impossible in C++. The advantage of this strategy is that the nursery never becomes fragmented. The first. The current Hotspot virtual machines does not even detect whether specific objects are short temporary ones that can be placed in registers or on the stack. unlikely that this will happen for a long time. except in the special case where the nursery is full. because Java does not allow explicit allocation of objects on the stack. Except for the fact that many allocations causes the garbage collector to run more often. meaning that the number of objects that needs to be copied to the next generation is expected to be small. The garbage collector used in modern versions of Java is a so-called generational garbage collector. C++ compilers cannot know this. heap allocation in Java is still much slower than stack allocation in C++. The second is where older objects are. 37 . But as can been seen from the benchmarks in chapter 7 the allocation and deallocation of a heap object is much faster in Java than it is in C++. often called the nursery. In the implementation that is used with current Hotspot virtual machines the heap is divided into two sections. one for each generation. if ever. The consequence is that an essential part of the current tweaking process for Java programs is to eliminate all heap allocations from performance critical code. It is.2.3 Allocation and garbage collection Another thing that often is said to be slow about Java is the garbage collector. The nursery is garbage collected by marking all reachable objects and then moving live ones to the next generation.

3. Remember. Fortunately. Furthermore.4. This prevents the long pauses from occurring. when you use a sweeping garbage collector (which is by far the most common) only live objects take time to process during garbage collection.4 The speed of Java in the future The first beta version of the Java platform was released to the public in 1995. there is only a very low risk of this actually happening unless the program allocation large quantities of objects. It is therefore no surprise that Java was very slow in the beginning. the risk can be reduced even more. Fortunately the area of the heap used for old objects will only need to be garbage collected rarely. and I will therefore only base the conclusions in this report on the current state of Java. Just within the last 2 years or so we have seen a 10-20% increase in speed (from JDK 1. it is in general dangerous to rely on guesses about future performance. However. That would admittedly be a bad thing in many cases. such as when a new level has been loaded.The section of the heap used for old objects uses a classic mark-compact garbage collector. If it is asynchronous then garbage collection occurs in a low priority thread. Objects that need to be deallocated are free. This does not remove pauses completely but makes them much shorter. if the garbage collector is synchronous it may cause pauses of a few seconds. even short pauses are bad in a real-time application since even minor variations in the frame rate will be immediately noticeable by the user. If you begin a project today in Java 38 . I often hear people say that they dislike the idea of a garbage collector that may kick in at any time and pause the system for a few seconds. garbage collection is not inherently slower than manual deallocation. Sun’s JVM 1. With almost every release we have seen vast improvements to speed of Java. When evaluating a language or system. However. It is actually often faster since you reduce the overhead of deallocating objects. not guesses about the future. especially in a high-performance real-time game.3. Garbage collection may be implemented in several ways. In contrary to popular belief. Fortunately.0). I think it is reasonable to assume that they within a couple of years will become about the same speed – if not Java even overtakes C++. the garbage collector is only working when the heap is full (or almost full). if one invokes the garbage collector explicitly at times when it doesn’t matter.0 to JDK 1. If the program is tweaked to avoid heap allocation in time critical code the risk of this happening is virtually zero. so the history of the system is fairly short compared to that of C and C++. And there are still many potential optimizations and improvements that still can be implemented. It is therefore reasonable to assume that this improvement in speed will continue with future versions of Java. 6.4 uses an asynchronous garbage collector. In essence it is often easy to write an application such that the garbage collector does not pose a problem. At the same time C++ compiler technology is only seeing minor improvements meaning that Java rapidly is gaining on C++ in performance.

When running programs compiled with a static native code compiler the program is not interpreted. Supported on all platforms that has a gcc port. the full JRE must be distributed with your application even if it is not used. These products only wrap the Java byte code into *. • gcj (A front-end to gcc and officially part of gcc. Swing and all the standard extensions (such as Java3D).3. it is not compiled at run-time and does not need a Java run-time environment (JRE). a lot. They do little or no actual compilation to machine code. Several Java static compilers exist out there. In this case.) • NaturalBridge BulletTrain • Instantations JOVE • Wind River Systems Diab FastJ • Tower Technology TowerJ (does not support AWT or Swing) • Metroworks Codewarrior for Java Some Java development tools try to give the customer/user the impression that they contain a static native code compiler although they do not.exe) files and even dynamic link libraries (*. A static compiler is a traditional compiler like those for C or Fortran that coverts source code into an executable file. These include: • Excelsior Jet. No Swing support. Static code compilers create regular executable (*.exe files. i. To these compilers. although some run-time dynamic link libraries may be needed‡. If one uses CNI then the code is locked to gcj.5 Static native code compilers Using Hotspot virtual machines is not the only way to improve the performance of traditional Java virtual machines. but you should not rely on it. 6. In addition to JNI it has its own native interface called CNI which is faster than JNI. Another popular approach is to use static compilers.so). Java is just yet another language that you can program in. Note that some static native code compilers require the final executable to use Sun's Java DLLs.dll/*.and Java is faster before your game is to be released that is only fine. AWT. Most of the mentioned Java static code compilers support everything from Java including all the standard libraries. A consequence of this is that programs compiled with static code compilers are no longer portable. Limited AWT support.e. This includes Webgain Visual Café (formerly Symantec Visual Café) and Microsoft J++. You can even import byte code supplied by third party developers and compile them directly to native code. due to JRE license restrictions. ‡ 39 .

An important question to ask is whether the code they produce is faster than Hotspot virtual machines.Static native compilation does not come without a price. As discussed in section 5.3. In practice this means that one should not rely on static compilers for your specific product. If you think about it. Sometimes static code compiler gives as much as a 30-40% speedup and sometimes they even slow code down. This approach is usually termed dirty Java. Since Java sometimes. 6. Also. One approach that gets the best of both worlds is to mix Java with C++ within a single application. use a different compiler for each platform. doing so would mean that one looses the productivity benefits on Java must also looses performance compared to those cases where Java is faster than C++. however. The source does not need to be changed. as seen in contrast to Pure Java. some of the static compilers do not support dynamic class loading. 40 . For such a program it can be risky to rely on Java for the performance critical sections of the code since to may turn out to be slow in that specific case. Some people prefer static compilers for deployment reasons. this doesn't make much sense anyway. The deployment of Java programs are discussed in more detail in chapter 8. is slower than C++ one may think that it is best just to ditch Java and write it all in C++.3. This is probably the case if you are building a brand new high-profile first person shooter. By using static native compilation the following Java features are lost: • No platform independence of the compiled code. • No support for applets. However. because there still is a lot of potential optimizations that is not yet implemented using that technology. Java static compilers are often not fully standard compliant. so if you need that feature you should make sure it is supported in the compiler you choose. since applets are supposed be put onto a web page and be able to run on many different types of machines. Static compilers cannot be used with Java applets . my personal guess is that in the long run Hotspot virtual machines are probably going to win out. If a static compiler can be used with success and it improves performance then this is fine. I recommend that you try it out for your specific application to see if any performance is gained.only applications. but one should not rely on it working or even improving performance over Hotspot virtual machines. But if they give your application any extra speed they are certainly worth the effort to try out. However. The answer is that sometimes they do and sometimes they do not.6 Dirty Java Often one has an application where certain parts of the program must run at a very high performance while it is less important with the rest. You can. even when tweaked.

1 * 4 + 0. Accessing C/C++ functions from Java does. • Wrong use of the API functions.[Kreimeier] discusses dirty Java this from a game development point of view. 6. So basically. It is likely that certain optimization techniques that the programmer used to make programs faster in earlier versions of Java and in other languages do not apply to Java and actually make programs slower. JNI allows Java to interact with C/C++ in a controlled way. This may give a slow down of has much as a factor of four or more. but it is still slow compared to native Windows applications) The first case. The performance of JNI is discussed in more detail in section 6.3. • The application uses an old version of the Java technology such a being run in the built-in virtual machine in popular browsers. Continuing the example from section 6. • Myths about tweaking. For instance.1 where we saw that a pure Java program in the worst case would be about 75% slower than C++. the main reason that Java programs are slow is that developers don't know how to use it properly. (Swing is supposedly up to 40% faster in JDK 1. This is also a problem for Microsoft Visual Basic which also is plagued by poor programmers. pools may actually slow 41 . Because of the generational garbage collector. I believe that it has to do with: • Java is easy to learn.3. Writing efficient programs can be bit tricky especially since many of the optimization "truths" you know from C++ no longer is true in Java due to the Hotspot adaptive optimizer. This maintains the protective nature of Java while allowing the Java programmer to access system specific features or third party libraries available only in C/C++. • The program uses AWT or Swing. happens more often than you think. not surprisingly.3) that the application is 30% slower (in the worst case) than had it been written fully in C++.4 compared to JDK 1.7. This is only true to some extent today. It is important to know what is fast and what isn't. This means that a lot of poor programmers learn Java and hence create poor applications and games. If the code than is run the most is written in C++ and the remaining 90% is written in Java and untweaked then the result is (0. that the program is poorly written or not tweaked. while C++ is not. In virtually all the cases it due to one or more of the following reasons: • The application is poorly written or the bottle necks have not been tweaked. The mix of Java and C++ is done through what is called the Java Native Interface (JNI).4 Why is this Java program slow? I often speak with people that has tried one or more Java applications and found them slow.9 * 1 = 1. have an overhead head compared to making the call directly from C/C++ itself. This article is highly recommended reading. it has long been a Java “truth” that using pools of objects was much faster than allocating heap memory each time it is needed. That is very slow.

According to [Hutchinson] a focus for the next version of Java might be to make it possible to share virtual machines automatically. which is quite compact.5 Memory footprint One issue that causes problems for Java performance today and probably also in the future is the large memory footprint. Finally. Java virtual machines are not easily shared among multiple Java applications. 6. • Because there is more heap allocations in Java and because these each tend to use more space on the heap than their C++ counterparts. More information about Java code tweaking can be found in [Wilson] and [Shirazi]. A rough rule of thumb is that a large Java application usually use a little under twice as much memory as equivalent C++ counterparts. This happens for several reasons: • The Java virtual machine and associated libraries needs to be loaded into memory. into memory and then use stack and heap space as needed. The extra heap allocations tend to comes from two sources: 1. The consequence is that this overhead is the same for each Java application that is running. All this makes the utilization of memory for C++ programs very good. It uses a few external libraries. on the other hand. because they are used by so many programs. uses much more memory. the focus was on optimizing their code for cleanly written code. This has led to the myth that cleanly written Java applications are the fastest kind of Java applications. rewriting the code to eliminate heap allocations is still an efficient way of improving the performance of a Java application. but these are usually stored in dynamically linked libraries and usually already loaded by the operating system. C++ programs can allocate system memory as they see fit and at any given time only use what is needed. Typical Java virtual machines use 5-10 MB memory each. When an array of objects is allocated in Java. This is simply not true. Java cannot allocate objects on the stack or even inside the other heap allocated objects. This means that Java programs use a lot memory just before garbage 42 . A C++ program generally only needs to load the executable image. This is required because of the garbage collector. In the design of the Sun virtual machines. • When a Java program starts the virtual machine is given a preset heap size that can be changed by a command line option. The overhead of allocating data on the heap is usually small. A Java program. often only a few bytes. This has. Currently. caused the allocation of heap objects to be much faster in Sun’s virtual machine than in IBM’s virtual machine (see chapter 7). 2. such as the C standard library. among other things. one first has to allocate an array of references to the object and the explicitly allocate each object it contains.things down in the long run since they will be moved to the second generation of the heap and cause subsequent garbage collection sweeps of the old object area to take longer to perform. For instance. the memory footprint increases even more.

This is a worse problem in Java than in C++ because Java provides less locality in its use of the heap. The Java Micro Edition is known for its low use of memory compared to the Java Standard Edition. if a Java application momentarily needs more memory than available on the heap then the application runs out of memory even if there still is system memory available. On PCs this high memory footprint of Java is not much a problem since many people today have system memory in abundance. The initial heap size may grow at run-time if garbage collection yields too little free memory until the maximum heap size is reached. On the other hand. For games it is not that much of a problem. there is not much room left for actual programs and data.2) for the Java Micro Edition that specifies how consoles are to work – treating them as a kind of embedded devices. on consoles this is a serious problem. a Sony Playstation 2 only has 32 MB of RAM. Sun and Sony is aware of this problem and has. Usually garbage collection is started automatically when the heap is about to be full. This is a profile (see section 4. 6. If the initial heap size ever grows to more than the available system memory disk swapping will occur. so there is hope for the future of Java on game consoles. Java and game consoles are discussed in detail in section 8. This issue is discussed in more detail in section 6. This happens because data is not deallocated immediately. begun work on what is called the Java Game Profile [JSR134]. However. For instance. which can access all the memory of the system. Current virtual machines usually work with two values: the initial heap size (defaults to 2 MB) and the maximum heap size (defaults to 64 MB). This could pose a problem for regular applications where the amount of data is unknown until it is loaded – a hex editor for instance. this means that it becomes vital to select a maximum heap size that is less than the available physical system memory. If one simply increases the maximum heap size to something very large (say 1 GB) then the performance will almost certainly drop since the system will begin to swap the heap to disk if grows to more than the available system memory.2. This means that the programmer actually must set a maximum amount of memory that the application may use at run-time.6 Selecting the heap size One problem with garbage collected heaps is the amount of memory that is allocated by the operating system is increased compared to systems that use explicit deallocation. together with several other companies.collection starts. This is very unlike C++.4. Essentially. This means that even if the application uses few heap allocated objects it might eventually fill up the heap using system memory for it all.6. but postponed until absolutely necessary. After the virtual machine is loaded.7. The programmer would simply require that the computer had a certain amount 43 . At this point the system will simply run out of memory.

of memory. Because the purpose of templates is accomplished by specialization. Java is so fast that this usually no longer pays because of the overhead involved in JNI. print this minimum amount on the outside of the game box. which was written using a mix of Java and C++. Java allows polymorphy using virtual methods of common base classes. However. the overhead was so small that it could not even be measured. This approach produces very efficient and elegant code and is therefore a much hailed feature of C++. [Wilson] 6. 6. This limitation is a clear draw back in Java and this issue will not be handled even when Generics are implemented. however. It is. Generics (see section 5. generation of machine code for each type that uses the template.5% of the overall processing time. true that JNI should not be used mindlessly. Alternatively. in later versions of Java. 44 . i. The added benefit of templates over macros is improved type checking and ease of debugging. Accessing Java from C++ is slow.19) are planned for JDK 1.5 but are not available in the current version. one can write a small C++ program that detects the amount of available physical memory and that then starts the game with a heap size a little smaller than that. This is not entirely true. In my own Master’s thesis [Marner01a]. In earlier versions of Java it was always an advantage to implement critical sections of the code in C++ (such as calculations in inner loops). setting the heap size explicitly for games is not much of a problem. This is seen as a major draw back of Java by many C++ programmers and Java programmers alike. In fact. this becomes just as efficient as had the code been written using a macro.19). With careful use this overhead can be brought down even further. Java does not support templates. in practice.7 The performance of JNI Some people claim that JNI is very slow and should be avoided at all costs. This approach can be used to write code that is similar to template code with the only exception that it only works with object types – not primitive types. but accessing C++ from Java is quite fast.e. For information about wrapping C++ libraries for use in Java see [Marner00] and [Marner01b]. C++.8 Templates / Generics When discussing the pros and cons of the performance of Java vs. all Java objects are derived from a common base class called Object. One Java developer I spoke with (Caspian Prince) showed me the profiling output of a partial OpenGL C++/Java interface wrapper that he had made and here the overhead involved with JNI was shown to be less than 0. and then set the maximum heap size to this value. So. Templates in C++ are provided primarily for one purpose: To make polymorphic functions/classes without the overhead of using a base class and having virtual methods. one inevitable has to discuss templates (see section 5.

• Every time more than one value is to be returned from a method. although there is nothing that prevents Java from competing with C++ templates. Unfortunately. But there is principally nothing that prevents the Java approach for being as fast as the C++ approach in future versions of Java. the Java approach is much slower than the C++ approach in current virtual machine implementations. This means that the Java programmer allocated objects on the heap in many cases when the C++ programmer does not: • Every time some temporary object is needed. also be able to detect if certain classes or functions only are used with certain types and then generate specialized versions of each class or function.2 for an example of this) To summarize. see the difference between the implementation 45 . This is not the case in Java. Hotspot should. so using generic classes in Java is often quite slow (see section 7. Examples of this can be found in the benchmarks of chapter 7. In Java primitive data types are passed by-value. this type of optimization is not yet implemented in Java virtual machines. so to pass it by-reference it must be encapsulated in an object that might be created for the purpose. current implementations of Java still lacks severely behind in this area. this does not currently always work well enough. String operations will therefore tend to be roughly twice as fast in C++ as in Java. In C++ tweaking usually does not change the appearance of the code much. 6. in principle. A speed up of 20-30% can usually be gained by doing this and it does not reduce the readability of the code significantly.With regard to performance. strings are represented internally as Unicode characters (2 bytes per character) while C/C++ traditionally represents them as ASCII characters (1 byte per character).9 Strings In Java.11. There are several reasons for this. but with Java a new object must be allocated on the heap. Just as virtual methods and explicit type casts can be eliminated. In C++ the programmer would just create a temporary object to be returned on the stack. The currently implemented optimization approach used in Java is to try to eliminate explicit type casts thereby making the code as fast as the C++ version.10 Tweaking and readability The consequence of tweaking Java code is greater than that of tweaking C++ code both with regard to performance and to the appearance of the code. C++ programmers can often just allocate them on the stack. Unfortunately. 6. • Every time a primitive data type needs to be passed by reference to a method. For instance. To eliminate all these cases can be a complex task. The greatest problem is that objects in Java cannot be stored on the stack.

In some cases Java is up to twice as fast as C++ and in some cases it up to three times slower. this makes it less clear to the reader whether arguments are ingoing. • To return multiple values the programmer can let the return value be passed back to the caller in an object that is passed by reference by the caller. since immutable objects by definition cannot change state. since very little of the code usually is run most of the time. Some of the tricks needed to eliminate the heap allocations are: • Allocate temporary objects outside the critical loops and reuse them by changing their state. but this may vary a lot.of Frustum.5 – 4 times slower than untweaked C++ code but this varies much. or both. and (3) objects can no longer be immutable. typically around 20-50% slower. (2) allocations does not happen in the code where the object is needed. outgoing. 6.java in the tweaked vs. Tweaking the code causes the readability of the code to be significantly reduced and hence also the productivity of the programmer. Unfortunately. Fortunately it is very often only necessary to tweak very little parts of the code. the untweaked version of the 3D Demo made with GL4Java. 46 . The drawbacks are (1) that this in some cases prevents the use of recursion. Untweaked Java code is usually 2.11 Conclusion Today tweaked Java code is a little slower than tweaked C++ code on the average.

7. but by focusing on the performance of problem solving rather than the performance of program instructions we will get more trustworthy results. so this will be included in the test. namely the Intel Windows PC. On this platform Microsoft Visual C++ is the de facto compiler. it is essential to use the compilers and virtual machines that provide the best performance – since these are the ones that will be used for production code. benchmarks can be a guide that helps to tell what the trend in performance seems to be.2 Choice of compiler/virtual machines When running benchmarks. The Intel C/C++ compiler is reputed as being the fastest C/C++ compiler available on the platform. I have considered including it in the test. Nonetheless. The best solution would be to write a major application or game in the two languages and then compare their speed when used by a regular user. These results are used.7 Benchmarks In this chapter we will run some benchmarks that compare the relative speed of Java and C++.3. I can only assure the reader that I have made a serious effort to make the benchmarks such that they do not favor one language or library over another. I have observed that people tend to become defensive whenever a benchmark shows that their favorite language or library looks bad. Code cannot be converted mechanically. too. 7. real-life applications may very well perform differently. so this will be included too. But it does not make sense to deduce anything about the speed of consoles based on the speed of gcc on the PC. doing so is a lot of work. Although I would very much have liked to perform benchmarks on various game consoles this has not been possible. to reason about the speed of Java in relation to C++ The full benchmark results are all listed in tabular form in Appendix I. Since gcc is one of the compilers that are used to compile for consoles. However.2). I have therefore chosen only to perform benchmarks on the primary desktop computer platform.1 Introduction The only proper way to compare two languages is to come up with a problem and then solve the problem in the two languages using the same general algorithms. because I do not have a development license for those machines (see section 3. so the benefit of 47 . together with the results from third party benchmarks. Benchmarks should always be taken with a grain of salt since they never completely represent full real-life applications. so the approach generally chosen is to build some smaller applications that are believed to represent larger applications fairly well. In fact.

For both C++ and Java the code is compiled using full optimization.0 SP5 Intel C/C++ 5. All of the used compilers and virtual machine can be downloaded on the Internet for free (Excelsior Jet and the Intel C/C++ is only available for an evaluation period) with the exception of Microsoft Visual C++ which must be purchased in retail.0 client hotspot Sun JVM 1.4.3.including gcc in the test is minimal. These cheats includes the deactivation of null checks.1_01 compiler followed by Excelsior Jet Professional 2.0 Sun JDK 1.0 server hotspot <is an executable> MS C++ Intel C++ Java 1.0 preview edition 25th sep 2001 compiler <is an executable> <is an executable> Sun JVM 1.4. Finally.4 client Java 1. JetPerfect was not used since 48 . except for JetPerfect. For Excelsior Jet maximum optimization.3. The following compilers and virtual machines are used in all the tests: Compiler Run-time system System Type C/C++ compiler C/C++ compiler Java VM Java VM Java static native code compiler Java VM Abbreviation Microsoft Visual C++ 6. optimizing for speed using maximum inlining (i.e.0 compiler Sun JDK 1. For Java I will use the latest version of Java from Sun.0 preview edition 25th sep 2001 virtual machine IBM I have also briefly tested the IBM Jikes compiler. was enabled including all cheats to make the program run faster.4.4. both the /O2 and /Ob2 command line options).1 IBM JDK 1. and checking of type casts.0 compiler so it is not benchmarked separately here. It seems to perform identically to the IBM JDK 1.3. array bounds checking. I will also include the latest IBM virtual machine since this generally is reputed to be fastest available.3. since this is the de facto compiler/virtual machine used by the community. To save time I have therefore not included gcc in the benchmark.4 server Jet IBM JVM 1.0 compiler Sun JDK 1. I have chosen Excelsior Jet to represent Java static native code compilers since it generally is considered among the best ones available.

no documentation about its use was included with evaluation edition of Jet. The full set of compiler options used for Jet is listed in Appendix I.

7.3 The system setup
The benchmarks were run on a single AMD Athlon 700Mhz processor with 392 MB PC100 RAM running Windows 2000 Professional SP2. The system contained an nVidia Geforce 256 video card (from LeadTek) with 32 MB RAM using the nVidia 23.11 reference drivers from December 2001. Note that the Intel C/C++ compiler is specifically optimized for Intel processors and should run somewhat slower on AMD processors. However, since AMD processors are very common among game players, I think using an AMD processor is acceptable for game benchmarking purposes.

7.4 The benchmark applications
In an attempt to produce trustworthy results, the goals of the benchmark applications should be: • They should be representative for the behavior of real-world applications. • They should be small enough to be worthwhile to implement in two languages. • If computational intensive libraries are used then the exact same libraries should be used in both cases. If this not possible then the libraries should not be used. • A least one benchmark should use data sets that are so large that the system as a whole cannot be placed within processor cache. • The benchmarks should avoid string handling. Java uses Unicode while C uses ASCII - comparing these would be to bias the benchmarks towards C++. We will not test the GUI system since it is a well-known fact that this is much slower in Java than in C++ Fortunately it is often not used in games. I have implemented three benchmark applications to fulfill the goals above: • A particle simulator: This measures raw computational performance. • An object oriented implementation of John Conway’s game of Life: This measures the performance of highly object oriented code where some heap allocations cannot be eliminated. • A 3D demo using OpenGL: This measures the performance of 3D hardwareaccelerated rendering.

49

7.5 Benchmarking both tweaked and untweaked applications
Traditionally, benchmarks compare only code that has been tweaked (i.e. handoptimized) by the programmer for optimum performance. This has the strong advantage that we can objectively say that both programs are of an equal quality - the best. When tweaking code I will require that it is done within certain constraints. For instance, if the point of a benchmark is to be representative for a big object oriented application then I should not eliminate the use of all objects for the sake of speed. Unfortunately, tweaked benchmarks are often not very representative for real world applications. Tweaking code is, especially in Java, a slow process and something that is more easily done to small applications than large ones. Real-world applications will only be applied limited performance tweaking and only in bottle neck areas of the program. Because a program very often has many internal dependencies, it can become hard to tweak certain areas of the program without making the rest much harder to maintain. So in general, the larger a more complex a program is the harder it is to tweak. Traditional benchmarks are usually small so it is usually no problem tweaking them - but if I tweak them fully then they stop representing real applications and their credibility as benchmarks are reduced. In order to compensate for this, I will benchmark both tweaked and untweaked code. I will let untweaked code be defined as code that is written according to the coding styles normally recommended for the language as taught in programming books. This will cause differences in performance that might seem unfair. For instance, in C++, users do not usually declare methods virtual that does not need to be so. Java users, on the other hand, rarely use the final keyword even if they don't expect to be able to use it as a virtual method. The purpose of untweaked code is to make a test that is representative for the majority of the code being written and run in the world. The majority of a real application will consist of such code, so doing this kind of test is very relevant. One might claim that an untweaked test cannot be compared fairly because of the differences in coding styles from programmer to programmer. I have nonetheless made an effort to write the applications in a fair way like I expect a typical programmer would do it (or like I would do it myself if the goal was cleanly written code). To summarize, untweaked benchmarks are not perfect, but neither is tweaked benchmarks and to accommodate for both points of view, I have included both in this report.

7.6 Warm-up periods
When making benchmarks that make use of JIT compilers and especially Hotspot virtual machines, one must take special care of the effects of warming them up.

50

During the initial warm-up period these virtual machines are significantly slower than statically compiled code. This will make these virtual machines automatically look bad in benchmarks that are run for a very short period of time. Since real-world applications are likely to run for long time, the warm-up period will have no significance in real-world applications. The benchmarks should therefore eliminate the effect of it in order to give a credible result. Most other benchmarks of Java that I have seen on-line make this fatal mistake and can therefore often not be trusted. We will review some of these in section 7.11. To eliminate the warm-up period, we will perform two steps: First, for each benchmark application we will explicitly measure the warm-up period on Sun’s 1.4 server JVM. This is the one that reputedly has the longest warm-up period. This can be done by measuring the execution times for linearly increasing problem sizes. When the growth in execution time becomes constant with each increase in problem size the warm-up period has ended. And just to be sure the test is not the victim of random variation, we will pad the measured warm-up period with some extra time. The measurement of the warm-up period assumes the warm-up period for a piece of code is independent of its exact use. This assumption does not hold in all cases, which also is a reason to pad the warm-up period with some extra time. Second, after the period has been measured, we will do the regular benchmark runs, running the runs for different problem sizes. In order to eliminate the effect of the warmup period we will only observe the performance difference by observing the difference in the growth in execution time each time the problem size is increased. This difference in growth will be a direct indication of the relative speeds of the two systems.

7.7 Generation of test data
In order to make it easy to scale the benchmarked problems in an easy way, the used data is randomly generated. Some third party benchmarks choose to generate the same test data every time, but this approach runs the risk of making some systematic biasing towards one of the implementations. Randomly generated data also has a small risk of doing this, but it is much less that if the same data is used every time. When using random data, one runs a random risk of producing data that will behave oddly when run. For instance, in the particles simulation below, if all particles start on top of each other they will collide and the result will not be very representative of a typical run. To compensate for this, all runs are repeated five times and the median value of the execution times has been taken. It would be incorrect just to take the average of the measured values, since a rare single highly deviating run would influence the final result.

51

7.8 A particle simulator
As the first benchmark application I have chosen to write a particle simulator. A set of particles (in this case planetary bodies) are put into 3D space with a given position and impulse. Gravitational forces are then made to work between them. When two particles collide they are combined into a new and larger particle. When a particle leaves the bounds of the world it is removed from the simulation. The computation has a time complexity of O(n2 * m), where n is the number of particles and m is the number of time steps. In order to avoid that the number particles are reduced late in the simulation we will set the size of each time step to something very small. This application is very computational intensive, and it should be seen as a representation of those parts of larger applications that contain mathematical computations. Only the actual simulation steps of the particle simulation is timed. The generation of the data set and the computation of the used time step are not. The benchmark does therefore not use I/O or any library intensively, with the sole exception of the standard library sqrt() function which is needed during the calculations of the force working between the particles. The source code can be found in Appendix B and Appendix C for the untweaked and tweaked versions respectively. The C++ implementation is a simplified version of a particle simulator that I wrote together with a fellow student, Søren Skov, as part of a university course in parallel programming and distributed systems in the autumn 2001.

7.8.1

Implementation notes

The set of particles is implemented as a linked list. This I have done because particles only are accessed in order and we need to be able to delete elements that we just have accessed. It could just as well have been implemented as an array. Doing so would likely have reduced the speed of the Java version (due to array bounds checking) but made no difference for the C++ version. The C++ version uses the STL list which is doubly linked list while Java uses a singly linked list. The List from the Java standard library could not be used because we needed to be able to copy iterators. The fact that C++ uses a doubly linked list while Java uses a singly linked list has extremely little effect on performance. The only consequence it has is that an extra pointer has to be set when a particle is deleted in C++. Particles are deleted extremely rarely in the benchmarks, since we, as noted in the previous section, set the time step to a very small value to avoid any particles from disappearing.

52

7.8.2

The untweaked version: Warm-up period

To measure the warm-up period of the untweaked Particles application I keep the number of particles constant at 300 and then increase the number of time steps gradually. Since this causes a linearly increase in time complexity, execution time should increase linearly too when the warm-up period is over.

Untweaked Particles warm-up
160 140 120 Seconds 100 80 60 40 20 0
0 20 0 40 0 60 0 80 0 10 00 12 00 14 00 16 00 18 00 20 00

Median First order derivation (x100)

Steps

From the graph above there is very little indication of any warm-up period going on. If it is there, it is certainly over after about 20 seconds including padding.

7.8.3

The untweaked version: The results

The following graph shows the absolute timings measured when running the benchmark. I have kept the number of steps constant at 100 and have kept the step size (h) constant at the low value of 0.001.

53

Untweaked Particles
160 140 120 Seconds 100 80 60 40 20 0
30 0 50 0 10 0 70 0 90 0 11 00 13 00 15 00

MS C++ Intel C++ JDK 1.4 client JDK 1.4 server IBM Jet

Particles

The fastest C++ version is Microsoft C++ and the fastest Java version the Sun JDK 1.4 client virtual machine. In order to eliminate the influence of the warm-up period in the comparison of these two programs, we look at the growth in time per growth in problem size. In other words we differentiate the graph above with respect to the number of particles. In order to avoid cluttering the graph, we only look at the two fastest versions:
Untweaked Particles Growth
Growth of Seconds / Particle 0.25 0.2 0.15 0.1 0.05 0
10 0 30 0 50 0 70 0 90 0 11 00 13 00 15 00

MS C++ JDK 1.4 client

Particles

Sun’s JDK has used 20 seconds when it is run with 500 particles and above so the comparison should be done only for 500 particles or above. We observe from the graphs that:

54

• The IBM JVM is in this case no faster than the Sun Hotspot JVMs. • It is the Java 1.• Here we see that the fastest Java version is approximately 3 times slower than the fastest C++ version. 7. This is surprising since we would normally have expected the Sun server JVM to be the faster of the two. Readability was lowered more in the Java program than in the C++ program. • Nothing is gained in this case by using a static compiler like Jet over a Hotspot VM.4 The Tweaked version: Introduction Both the Java and C++ versions of the Particle program have here been tweaked for performance.8. 7. For Java the same change was made and during this process all heap allocations was eliminated from within the timed part of the program. Instead old vector objects were reused when possible. The change that caused the largest effect for the C++ version was to change the implementation such that as few as possible new vector objects were allocated at runtime.4 client that is the fastest JVM.8.5 The tweaked version: Warm-up period Repeating the warm-up measuring process using the tweaked version of the program using Sun’s JDK server VM we get the following result: Tweaked Particles warm-up 140 120 100 Seconds 80 60 40 20 0 0 20 0 40 0 60 0 80 0 10 00 12 00 14 00 16 00 18 00 20 00 Median First order derivation (x100) Steps 55 . This is a bit surprising since it generally has the reputation of being the overall fastest JVM available.

06 0.6 The tweaked version: Results Again the number of steps is 100 and the step size is 0.08 0. We can therefore safely assume that it is over after 15 seconds.4 client JDK 1.12 0.02 0 30 0 50 0 10 0 70 0 90 0 11 00 13 00 15 00 MS C++ Jet IBM JDK 1. 7.4 server IBM Jet Particles Again Microsoft C++ is the fastest.1 0.04 0. although it probably only lasts a few seconds.Here there is absolutely no indication of a warm-up period. Tweaked Particles 120 100 Seconds 80 60 40 20 0 30 0 50 0 10 0 70 0 90 0 11 00 13 00 15 00 17 00 MS C++ Intel C++ JDK 1. The fastest of Java versions are this time Excelsior Jet closely followed by IBM’s VM and Sun’s JDK client VM.8.001.14 0.4 client Particles 56 . Tweaked Particles Growth Growth of Seconds / Particle 0.

11. The IBM VM is the fastest regular VM this time (Jet is a static compiler and therefore not what is considered a regular VM). Java has seriously gained on C++. being about 30-50% slower than C++. 7. That the speedup was minor for C++ was expected. Besides handling many heap allocated objects. 57 . Cells are derived from an abstract class Entity that can be used to implement cells with other kinds of behaviors. however. I have therefore implemented Life such that each cell is an object and this object will.2. this benchmark is characterized by having a high memory requirement. they are still something that should be avoided in optimized code if at all possible. The major speed up from Java comes from the removal of all heap allocations from the inner loops. The starting world is generated randomly.The tweaking caused more than a double up in the speed of the Java program but only a 5-10% speed increase for the C++ program. in the benchmark in this report I wanted to focus on the performance of object-oriented code. In most cases the number of cells will rapidly drop within a few time steps and then end in a steady state or even oscillate between a few states. So although Java heap allocations are not as expensive as they were in previous versions of Java. This is a little surprising since it performed quite poorly in the untweaked version. This time the Jet version is the fastest of the Java versions. check whether it should die or whether it should spawn new neighbors. The computation speed used per position in the grid per step will depend on whether there is a cell in it or not. The time complexity of the algorithm is O(n2 * m). However. Traditional implementations usually represent the world as a 2D array of booleans and then update it procedurally. The IBM VM and the Sun client VM follows just after being about 60% slower. Such a third party benchmark suite comparing C++ and Java is reviewed in section 7. As expected. I can only conclude that IBM VM does not handle things such as heap allocations very well. when time passes. Each position in the world initially has 40% chance of containing a cell. Java has not quite achieved C++ speed but is not very far of. where n is the side length of the two dimensional world and m is the number of time steps performed. but this does not change the complexity of algorithm. there is no effect of the warm-up period in this application so the relative positions of the programs in the growth graph directly mirror the positions in the graph with absolute timings. After I tweaked the programs.9 An object-oriented version of Life The next application is an implementation of John Conway's game of Life [Gardner70] [Gardner71]. it is the fastest regular VM there is. For raw computations. The purpose is to tell the speed of cleanly designed object-oriented applications.

This means that if the initial heap becomes too small then the heap will grow until 200 MB in size until it accommodates the needed data.7. but doing so would cause much memory to be used even when it is not needed and has therefore been avoided in these tests to avoid causing a bias in favor of Java. which sets it to 200 MB. Untweaked Life warm-up 180 160 140 Seconds 120 100 80 60 40 20 0 50 0 10 00 15 00 20 00 25 00 30 00 35 00 40 00 45 00 0 Median First order derivation (x500) Steps From this graph it is clear that there is a warm-up period. So when the growth in time becomes constant the warm-up period has ended. This sets the maximum size of the heap to 200 MB. Since we expect the cells to oscillate between a low number of states after a few time steps we can assume that the computation time is linearly dependant on the number of steps.2 Untweaked version: Warm-up period To measure the warm-up period we keep the side length of the world constant at 200 and vary the number of time steps computed. To handle this. Although the program seems to become a little faster after 100 seconds this is only temporary.9. so I estimate that it in fact already is linear at this point. Unfortunately it is fairly hard to see when it ends. In 58 . For Excelsior Jet the maximum heap size was set using the compiler parameter heaplimit=209715200. 7. the regular Java virtually machines are run using the non-standard parameter –Xmx200MB. A little more performance could be gained for Java by also using the –Xms parameter.1 Implementation notes The default maximum heap size in the latest versions of Java virtual machines is 64 MB (this value is JVM dependant). This is not enough to run this benchmark when the world gets bigger.9. The source code can be found in Appendix D and Appendix E for the untweaked and the tweaked versions respectively.

4 server.25 0.3 Untweaked version: Results Untweaked Life 250 200 Seconds 150 100 50 0 MS C++ Intel C++ JDK 1.9. These two are shown below.2 0.05 0 Intel C++ JDK 1.4 server 10 0 30 0 50 0 70 0 90 0 11 00 13 00 World size 15 00 15 00 59 . although there is not much difference between that and Microsoft’s compiler. 7.15 0.1 0. differentiated with respect to the size of the world: Untweaked Life Derived Growth of Seconds / World size 0. the curve already seems to be linear after about 70 seconds.4 client JDK 1. The best VM is Sun’s JDK 1.4 server IBM Jet 10 0 30 0 50 0 70 0 90 0 11 00 13 00 World size The best C++ compiler for this benchmark is Intel C++.fact. To be safe in case of variations in data we will assume that the warm-up period is 90 seconds.

we again get a poor performance with IBM VM. This is probably due to the fact that the C++ uses actual heap allocations this time and can't put all the data on the stack as it could in the Particle simulator. Java compares better because its allocation and garbage collection is fast compared to explicit allocation/deallocation. Like in the untweaked particle simulator. Using Jet in this case made the program several factors slower and it seems to scale even worse. From the differences in growths in the graph above we see that the fastest Java is 2 – 2½ times slower than the fastest C++. If we had allowed such elimination it would be possible to reuse the Cell objects (like in the Flyweight pattern [Gamma]) and this would probably have improved performance significantly. In this benchmark we see the approximately same behavior as in untweaked Particle Simulator.9. So in highly object oriented systems where C++ would also have virtual methods and abstract class and many heap allocations. This strengthens the hypothesis that the IBM is not very good at doing heap allocation. 60 . though. These have not been eliminated on purpose. After the tweaking has been performed there are no longer any immutable objects.4 The tweaked version: Introduction The Life program has now been tweaked for performance. 7. because such elimination often is impossible in large complex object oriented systems. Cells are still allocated on the heap. Java compares better this time though.The warm-up period has ended for the Java server VM at 90 seconds and this is at a world size of about 1300. This benchmark also shows that static Java compilers should not be used blindly. We should only compare their relative speed after this period.

7. 7.6 The tweaked version: Results Tweaked Life 60 50 MS C++ Intel C++ JDK 1.9.5 The tweaked version: Warm-up period Tweaked Life warm-up 160 140 120 100 80 60 40 20 0 Median First order derivation (x500) Seconds There is absolutely no indication of a warm-up period in this program.9. For the remainder of this benchmark we will work with the assumption that the warmup period is over after 30 seconds – it is probably much less.4 server IBM Jet Seconds 40 30 20 10 0 70 0 30 0 10 0 50 0 90 0 11 00 13 00 15 00 17 00 19 00 21 00 World size 0 50 0 10 00 15 00 20 00 25 00 30 00 35 00 40 00 45 00 50 00 Steps 61 .4 client JDK 1.

The Java version became much.05 0. This also allows us to compare the relative speeds of GL4Java and Java3D. much faster.02 0. To achieve this for the Java3D version I 11 00 13 00 15 00 17 00 19 00 21 00 World size 62 . Jausoft GL4Java.06 0.4 server 50 0 10 0 70 0 30 0 90 0 After we performed the tweaking the C++ program gained about 25% in speed. drivers. Since modern games.04 0. In C++ the benchmark application will be implemented using OpenGL.4 server VM and the fastest C++ is Intel C++.4 server VM is 2-3 times faster than the fastest C++ version. and low-level graphics API can be important factors when running such a benchmark. On the Java side it will be implemented in two versions. and in order to eliminate the effect of these I will run them on the same 3D hardware using the same driver. The 3D hardware. often consist of large static levels this is where I will focus this benchmark. 7. such as first person shooters. The other will use Java3D. Tweaked Life Derived Growth of Seconds / World size 0.01 0 Intel C++ JDK 1. It will use GLUT for portability.10 A 3D demo The third and final benchmark application used in this report is a 3D non-interactive demo that attempts to be representative of 3D applications or games. scaled. Tweaking the Life application approximately yielded about a factor of 10 in speed! This shows just how important it is to perform tweaking of Java code.The fastest Java is again the Sun 1. A world is created and it is populated with a given number of randomly positioned. while it is not so important for C++ code. and rotated boxes. The camera then flies through the world in a circular motion rendering them as fast as possible. One will use the current de facto OpenGL bindings for Java.03 0. The application running on the Sun JDK 1.

• Flat shading. • A global ambient light and a single directional light (phong lighting model) with no distance attenuation. The following screenshot was taken from the C++ version in a world with 8000 boxes: 7.1 Implementation notes All three implementations of the 3D Demo use the same 3D features: • Linearly attenuating fog. • Perspective projection with the same view frustum (a 90° wide field of view) • Textures. 63 . mipmaps.10. • Nicest perspective correction (The same terminology is used in both Java3D and OpenGL). This is also reputedly faster than the DirectX version. and trilinear filtering. • Back face culling.will use the OpenGL version of Java3D.

The culling algorithm in the OpenGL versions scans the kd-tree and discards and boxes that lies outside the view frustum. This means that the C++ and GL4Java version. For a discussion on culling algorithms see [Assarsson]. No vendor specific extensions were used. use a scene graph. The Java3D version could not be tweaked much. much like the Java3D version.2 was used for the GL4Java version. version 1. For Java3D. the other implementations only uses OpenGL features available in OpenGL 1. Java3D already does this automatically so here only the correct bounding boxes needs to be set. This makes the texture look lit. To make the application more like a real 3D application. the actual vertex positions are changed in a preprocessing step and these are then entered directly into an OpenGL display list. It has therefore only been included in the comparison of tweaked code. The bounding boxes in both Java3D and OpenGL are identical. Rather. (This assumes that no capability bits are set and no geometry is stored by reference in that branch of the scene graph. GL4Java version 2. Since the world is static. Then a regular bounding box vs. so the performance is identical in its tweaked and untweaked versions. the box positions in the OpenGL versions are not set each frame by changing the OpenGL modelview matrix.8. The plane of view frustum that was last used to cull the given bounding box is tested first. • The view port is 640 x 480 pixels. A big effort has been made to make each of the implementations as identical as possible and for the tweaked versions to make them as fast as possible. The culling algorithm uses two steps: First it does bounding sphere collision detection between the view frustum and the bounding box (the sphere extends to the corners of the box). 64 . The C++ and GL4Java versions supports asymmetric view frustums while the Java3D version only supports symmetric view frustums. view frustum culling is performed. it also implements view frustum culling. These assumptions hold in the benchmark) In the Java3D version I have not manually merged all Shape3D objects at each branch group. According to Chien Yang of the Java3D development team merging is done automatically during scene graph compilation as long as their Appearance object are shared – which they are in this benchmark. The C++ and GL4Java versions do therefore not implement occlusion culling like I am told that Java3D does.• The drawn surface colors are a modulation of the light and texture colors.3 beta 1 was used since this according to the developers is the fastest version available.1 and earlier. This gives a small bias towards Java3D. For the OpenGL implementations a kd-tree data structure was used to subdivide the world [Berg]. Java3D does this automatically in our case. although the use of certain nVidia OpenGL extensions would have increased speed of the C++ and GL4Java implementations significantly. To make the comparison fair to Java3D.

Most of the time occurs in hardware. 7. The source code of the untweaked and tweaked versions can be found in Appendix F and Appendix G respectively. as would normally be expected.5. Static compilers are written from scratch by much smaller companies and have therefore not been as vigorously tested as the Sun virtual machine or derivatives.3 Untweaked version: Warm-up period Untweaked Virtual World warm-up 50 45 40 35 30 25 20 15 10 5 0 Seconds Median First order derivation (x500) There is very little indication of any warm-up period in this program. is that the world size stays constant when the amount of geometry increases.3 beta 1 uses certain JDK 1.10. This causes the amount of geometry within the view frustum to increase linearly with the number of boxes. that one should not rely on being able to use static native compilers for one’s code. as discussed in section 6. Whether this is actually the case.2 Excelsior Jet For both the tweaked and untweaked versions of the GL4Java implementation and the Java3D implementation I was unable to get Excelsior Jet 2.3.4 features if they are present. The reason why it is not O(log n * m). This seems to be related with the fact that Excelsior Jet 2.The complexity of the algorithm used in this benchmark is O(n * m) where n is the number of boxes and m is the number of frames. This issue emphasizes.3 while both GL4Java and Java3D 1. 7. I do not know.1 only supports Java up to JDK 1. This is expected since only very little of the execution time in a GL4Java program occurs in Java. when one uses a tree culling algorithm.10. Just to be safe we will assume that the warm-up period is over after 10 seconds.1 to work. 0 10 00 20 00 30 00 40 00 50 00 60 00 70 00 80 00 90 00 10 00 0 Frames 65 . or whether it is something I have done wrong.

7. That will only happen in the benchmark of the tweaked version in section 7.10. that class runs almost an order of magnitude faster.6 Tweaked version: Warm-up period Since the Java3D implementation is the implementation with absolutely most code written in Java (most is within Java3D itself) I will use this version to measure the warmup period. but it is almost impossible to read.10. 7. The x-axis is a logarithmic scale. This has been done so we can examine how the performance is at both very low and very high amounts of geometry. the tweaking of the Frustum class (Frustum.10.7. This is very likely due to the fact that most of the execution time is spent in 3D hardware.5 Tweaked version: Introduction Again the main tweaking method I used was to eliminate all heap allocations in the Java program.10. The results above shows very little difference between the different languages and compilers/VMs. This makes untweaked Java more interesting for 3D applications. because the overall performance consequence of using untweaked code will be minor.4 server IBM Recall that the Java3D implementation is not tested here.4 Untweaked version: Results In this benchmark we keep the number of frames constants at 3000 and vary the amount of geometry.java) is a clear example of how tweaking Java code reduces readability. The C++ had no heap allocations.6 and 7.4 client JDK 1. Untweaked Virtual World 250 200 Seconds 150 100 50 0 MS C++ Intel C++ JDK 1.7. so here was very little to improve. After tweaking. 10 24 20 48 40 96 81 92 16 38 4 32 76 8 Boxes 25 6 12 8 51 2 32 64 66 . In the GL4Java implementation.10.

7 Tweaked version: Results Tweaked Virtual World 300 250 Seconds 200 150 100 50 0 MS C++ Intel C++ JDK 1. 7. Just to take care of any variation that may occur we will assume that the warm-up period is not fully over until after 25 seconds. This caused a lot of pauses during those initial 15 seconds after which the pauses disappeared.Tweaked Virtual World warm-up 50 45 40 35 30 25 20 15 10 5 0 Seconds Median First order derivation (x100) 40 0 80 0 After a clear warm-up period the graph becomes linear after about 15-20 seconds.4 server Java3D IBM Java3D 64 12 8 25 6 51 2 10 24 20 48 40 96 81 92 16 38 32 4 76 8 Boxes 32 12 00 16 00 20 00 24 00 28 00 32 00 36 00 40 00 Frames 0 67 .4 server GL4Java IBM GL4Java JDK 1.4 client Java3D JDK 1. While making this test it was clear that system compiled the Java classes during the initial frames.10.4 client GL4Java JDK 1.

5 is very much.5 times slower than both GL4Java and C++ even when amount of geometry increases. The performance chapter (Appendix D) in the first edition of Bruce Eckel: Thinking in 68 . In this case. which make it hard to discern the individual curves in the chart. We can therefore use the absolute values in the graph above directly. The fastest versions are the two C++ versions. This is incorrect on any modern processor because of processor pipeline issues. The overhead is especially severe since most of the execution time is supposed to be occurring in hardware. measuring the performance of single instructions.11 Reviews of third party benchmarks This report is not the first paper to contain benchmarks that compare Java and C++. I had expected that the overhead of Java3D was more or less a constant period of time. It remains 2. This naturally assumes that the OpenGL application has been fully tweaked for performance. and this will become harder to do as the complexity of the application becomes higher. but a factor of 2. Instead of just relying on my own benchmarks I will review some other benchmarks in this section.In the chart above all the curves are clearly grouped into two distinct groups. This means that the overhead of the Java3D API itself is many more times higher than the overhead of GL4Java. The GL4Java implementation is a little slower but they all stay within the same main group of curves. however. 7. One should always take a benchmark with a grain of salt since it not completely represents a full application. Here are some of the pitfalls that I have observed that third party benchmarks sometimes commit: • The use of extremely short benchmarks.1 Evaluating benchmarks When looking at Java benchmarks made by third parties one should always be skeptic since those benchmark might not always convey a result that is representative of realworld applications. I will review their validity as fair benchmarks and summarize their results taking into account technology changes since they were originally published. The warm-up period is already over for the lowest number of boxes so the whole graph is valid. Within each group the compilers/JVMs all perform almost identically. The Java3D version is about 2. This is surprising. the result is so clear that it cannot be ignored: I can only conclude that using Java3D is significantly slower than using GL4Java or C++ with OpenGL. possible repeated many times to make time measuring possible. but according to the benchmark results Java3D does not scale any better than the other implementations.5 times slower than the C++ and the GL4Java versions.11. I expected scene graph APIs to have an overhead. 7. Much slower is the Java3D implementation of the demo.

• Benchmarks using old versions of Java. For the sake of this review I downloaded the two programs C++ pointer and Java array. The author has probably used the wrong compiler options as discussed in section 7. This is not my experience. In Microsoft C++ this often happens because the standard setting for maximize speed does not aggressively inline code. This happens often when Java programmers want to compare C++ and Java.html This page contains a number of benchmarks in C++ and Java of: • Various implementations of a generic bubble sort algorithm. Benchmarks should preferably measure the warm-up period explicitly and take it into account.Java [Eckel98] made this error. To make the inlining more agressive one must include the /Ob2 parameter. So always check the code to see if it appears fair before believing in validity of a benchmark. • Benchmarks that use random data but does not use repeated tests. In the paper Microsoft C++ compares very poorly to gcc.edu/uli/java_cpp.3 was used in the tests. not surprisingly.stanford. • The built-in in library sorting functions in Java and C++. This chapter was. • Benchmarks that are run for very short periods of time – often just a few seconds. According to the benchmark results in the paper Java is 4 times slower than C++ for these two applications. Since there have been significant advances in Java technology since the original release in 1995 it is vital to use the latest version of Java to make the comparison fair.11. Although no clear conclusions are made in the paper it seems that the results say that C++ is about 4-8 times faster than Java for the bubble sort and about 30% faster for the library sort.1. JDK 1. Running multiple Java benchmarks in the same VM session does not solve this problem but may give a bias towards runs that is run late in the session. They neglect to turn on all optimization in C++.11. 7. while the code could actually be tweaked more. These will often vary so much from run to run that single results cannot be trusted. This way the warm-up period has not yet stopped and Java benchmark will look ridiculously poor. 69 .2 Benchmarks by Ulrich Stern Can be found at http://verify. • Not using the best compiler options. The default setting will only inline functions and methods declared with the inline keyword. removed in the second edition [Eckel00]. The library sort is run for only a short while on the server VM and can therefore not be trusted. • Benchmarks sometimes give the impression that they use tweaked code. These benchmarks were once relevant but are not any more.

5 slower than C++ depending on the amount of tweaking one is willing to perform on the Java code.0 preview 41. Both C++ pointer and Java array was rewritten such that only the actual sorting. 113.php?article_id=153 This benchmark compares C and Java by running three applications: • An array implementation of the Game of Life 70 .3. 7. if one is willing to use unorthodox means then much speed can be gained anyway.4 server IBM JDK 1. The edited source code can be found in Appendix H. If I had written the Java version to maximize for speed I had either written specialized versions for the sorting algorithm for each type of object to be sorted or I would have used a third party Java preprocessor to do the specialization.5 – 2.48 sec.C++ pointer is a template version of a generic bubble sort and Java array is a generic bubble sort algorithm that works on an array.01 sec. Java uses array bounds checking which C++ does not.4 server 61. not the data generation. Furthermore.76 sec.com/Spades/read. because it cannot generate specialized version of code like C++ templates can. If explicit specialization is added to the Java version so it now only can sort Range objects (this can be done generically by using parametric macros using a third party Java preprocessor) and reorganizing the code a bit to make it more Hotspot friendly the result is: Sun JDK 1. It is therefore no surprise that the Java version is much slower than the C++ version.aceshardware.88 sec. was timed. So the conclusion that I derive from this benchmark is that Java is 1. This just shows us that current versions of Java is poor when it comes to generic algorithms. The results without explicit specialization were: Microsoft C++ Intel C++ Sun JDK 1.11. 43.78 sec. 104.3 Benchmarks by Chris Rijk Can be found at: http://www. For the sake of this review I have rewritten Java array in order to eliminate some unnecessary array accesses and have increased the number of elements to sort to 3000 in order to reduce the significance of the warm-up period. However. Due to the superiority of current C++ template implementations this causes specializations of the code to be made that are currently not done by the current Java compiler technology (although it might be added in the future).

We will only review the first here in detail. This causes inferior optimization and according to my experience usually a slow down of 10% or more. 7. The C vs. Doing File I/O is mostly operating system dependant as is therefore not really interesting. Sosnosky Can be found at: http://www. This should be taken into account when looking at the benchmark results. This makes the right sides of the graphs in that paper more trustworthy than the left sides. In the conclusion the author wonders why Microsoft C++ performs poorly in the tests. I have rerun some of the benchmarks provided with the paper and get results that are consistent with those given in the graphs. Microsoft C++ was used for the C code. but run for only short periods of time and runs multiple benchmark in one session.11.html This set of benchmarks aims at measuring the performance of some very specific tasks.4 Benchmarks by Osvaldo Pinali Doederlein Is called the Java Performance report IV and can be found at: http://www.html This report only contains limited discussion of Java vs.5 Benchmarks by Dennis M. • File I/O. Java comparisons that is present in the paper general state that Java and C are of the same speed. but in many cases there is little difference among them. C benchmarks but mostly focuses on comparing Java regular virtual machines with Java static compilers. The missing optimization option is probably the reason. This is probably due to the fact that I used Sun JDK 1.4 and the article uses Sun JDK 1. When going over the used compiler options I noted that Microsoft C++ used the /O2 option but not the /Ob2 option. 7. I also got better results for Microsoft C++ due to the added optimization option. The benchmarks are generally fair written. The memory exercising benchmark is 71 . in fact in many cases my Java runs was relatively faster than those shown.org/fr/html/frm/javalobby/features/jpr/part4.com/Java/Compare. The things measured are: • Factoring of high numbers. • Memory exercising.javalobby. It is sometimes faster and sometimes slower.sosnoski. Several factoring methods are used so this program uses many conditionals.• Calculation of Fibonacci numbers • Fast Fourier Transforms. This is among other things why Excelsior Jet was the static compiler of choice in this report.3. The general conclusion that one can derive from this benchmark suite is that Java is more or less the same speed as C. Of the static compilers Excelsior Jet generally seems to perform best.11.

6 Benchmark by Mads Pultz Can be found at: http://hjem. The only conclusion is .12 Conclusions and thoughts After running the benchmarks made for this report and reviewing the benchmarks made by other people we can conclude that: 72 . quite well written and should be trustworthy as a general Java vs. I first suspected that the author did something wrong when compiling the C++ version.4 was used in the test. I therefore do not consider this benchmark valid. but in later versions the C++ versions was improved more and the end result is that Java roughly 130% slower than C++.that Hotspot compilation did not help at all with this benchmark. This is generally not surprising.get2net. The benchmarks seem to well written. because memory is only allocated once and never actually used.heavily biased towards C++.11. 69 sec. Benchmark Factors FactorsF Sun 1. The application is available in multiple versions with varying degrees of tweaking. FactorsF is the same program as Factor but uses floats instead of ints for the calculations.4 server 139 sec. but when I tried the precompiled binaries that is provided with the benchmark I see that this is not the case. Here the fastest Java is about 10% slower than the fastest C++. I have recompiled both the Java and C++ and rerun the tests to see the results in a modern context. 63 sec. do not include the /Ob2 parameter to Microsoft Visual C++. 342 sec. however. 72 sec.consistent with the author’s own conclusion . This causes Java to use much time to grow the heap from the initial 2 MB to size needed in the benchmark. as so many other benchmarks. 7. 76 sec.8 and 1.3 preview 121 sec. A beta version of JDK 1.2 compared to C++. The first versions of the benchmark shows Java to be approximately 40-50% slower than C++.dk/mpultz/ This benchmark compares the performance of matrix multiplication in C++ and Java. IBM 1. However. MS C++ 6 118 sec.4 client 485 sec.1. The factoring benchmarks are run using only old versions of the Sun JDK and Microsoft Visual C++. C++ benchmark. Intel C++ 5 112 sec. Sun 1. The first benchmark is. The only thing that I can criticize is that it. it is surprising how good results the benchmark author got in his runs with JDK 1. 7.

In principle Java could do the same. • Java programs run slowly in the beginning. to try to determine which parts of the program that will have the highest throughput and then make the design such that these parts are isolated cleanly from the rest of program through well-defined interfaces. • Java code that is written to be tweaked for speed runs much faster. C++ programs can use templates to generate specialized versions of the code. typically around 20-50% slower. tweaked Java and because tweaking Java code tends to be harder than tweaking C++ code. 73 . Tweaking is therefore not something one would want to do for all of the code in a large application. during the design of the application. It ranges from being two times faster than C++ code to being two to three times slower. • Java in general has a lot of problems competing with C++ when it comes to generic code. where this usually will result in a number of short pauses during the first frames to be rendered. Given the big difference between the performance of untweaked vs. it is important to avoid that the influence of the tweaked code spreads to the untweaked parts of the program thereby decreasing the ease of maintaining the rest of the application. • When tweaking Java code the code looses much readability. To prevent this. but the current virtual machines do not implement this optimization. there would not be a productivity benefit by using Java.5 to 4 (less in the case where most execution time occurred in 3D hardware). The tendency seems to be that Java is a little slower than C++. Sun's Hotspot JVM is much faster at heap allocation than the IBM JVM and Excelsior Jet. This will allow the programmer to tweak those parts without affecting the rest of the application. In our case it was factors varying from 2. This can be annoying for 3D applications. but this varies much.• Code that is written in a straight forward way as intended in Java (as suggested by various programming books such as [Eckel00]) and C++ (as suggested in [Stroustrup]) will typically be significantly slower in Java than in C++. If one did tweak everything. I expect this more or less represents the slow down that will be experienced in the major parts of any large application. • The IBM JVM and Excelsior Jet are more efficient at doing computations than Sun's Hotspot JVM. it is important. One should choose the one that suits the application in question best.

why this system is better than the one currently used. [Byous] [Wells] According the IDC study software projects written in 100% pure Java instead C++ yield a 25% overall time/cost saving cost! This corresponds to a productivity increase of 30%. but is a clear advantage when you need to hire new people or when you want the some of non-programmers on the team to do some of the programming. but it is a fact that Java increases your productiveness both by increasing code writing speed but also by reducing the number of bugs. and consider whether it is advantageous to do so. It is not clear how big the increase productiveness is for games. if not the main goal. The reasons for this are many and they were discussed in chapter 5. I was surprised how much faster development in Java really is. from the beginning and has been an influencing factor ever since.1 Why use Java? Before you begin to use a new system you should always ask. 74 . You may think that the advantages of Java may seem very few. This is not so for Java. The mere fact that so many companies are changing to Java for business development strengthens this argument. This is of course no benefit to programmers who already know C++. IDC Bulletin #W16212. and if you are very productive in C++ you may think that you can live without them.8 Evaluating Java for a game project In this chapter we will bring together the information from the previous chapters and discuss how and why Java can be used for games. I have not been able get access to this report first hand and have instead relied on second-hand accounts about it. There is absolutely no doubt that programmers are more productive in Java than in C++. [Gosling96] Besides these reasons an added benefit that stems from the advantages presented in chapter 5 is that Java is easier to learn. I also thought so before I learnt how to use Java several years ago. Unfortunately. The improved ease of use and increased programmer productivity was an important goal in the design of Java. In May 1998 IDC published a large study: Java Technology Pays Positively by Evan Quinn & Chris Christiansen. This is quite a significant number. and is therefore not something game content providers usually are willing to learn. C++ is notoriously hard to learn for beginners. This I can confirm from personal experience. For Java the answer is that you will increase programmer productivity. This corresponds to productivity increases of approximately 67% and 42% respectively. The saving was 40% in the coding phase and 30% in the maintenance phase. 8.

John Carmack (one of head programmers for ID software) spoke about the importance of high level tools and languages in development today.0. I remember reading a magazine interview with a game developer in those times where it was stated that DOS would always prevail because games needed all the speed they could get. That John Carmack does consider Java a good language is clear from this quote: "We are still working with significant chunks of an existing code base.2 Considering the risks Before choosing to rely ones business on a new system. These and other potential risks will be discussed in this section. at QuakeCon 2001. It was too slow for games. I cannot know the exact context in which this numbers was calculated. one must always be careful that the does not have some fatal flaw. Even further back in time everybody used assembler and didn't like the idea of using C. Since I do not have access to the actual report.The results in the IDC study assume that the project use 100% pure Java. There are a number of other articles that compares C++ and Java developer productivity. productivity tends to mean more and more." [Kreimeier] 8. When the programmers get more skilled the productivity should increase even more. except for certain isolated sections of the code. To me these numbers seem exaggerated. Today no sane developer would use assembler for a full commercial game. This is often not the case with games. Then in 1993 ID Software released Doom. assume that the applications have been tweaked somewhat for performance. [Singal] [Tyma] [Phipps]. He said that Java may very well be the de facto language for game development in the future. the applications would have been extremely slow back then. network programming. If they were not. They all find that Java gives higher developer productivity than C++. containing less than 5% assembler. You can probably remember the time when nobody wanted to make Windows programs because DOS was in full control of the machine and that was the way to do it. I have heard several Java developers claiming productivity increases from a factor of 2 to 10. however. Common flaws of apparent brilliant systems are that they are very buggy or that no proper documentation is present. It should also be noted that the IDC report analyzed projects where the programmers were new to Java. say. 75 . I will. the Java has had some significant improvements since those project was made (They used JDK 1. In fact.2) so this should more or less compensate for this fact. All in all I would say that the numbers from the IDC seems reasonable and can be applied to games too. On the other hand. That was a turning point for game developers who until then had written much code in assembler. As time goes by. such as. they said. If I did want to go off and start fresh. I would likely try doing almost everything in Java. Doom was written almost solely in C.

articles and books.2. 8. At the Game Developers Conference 2001 it was announced in a Sun press release [Shuster] that Sony and Sega. Sun. including the Java static compilers are often less stable and reliable. Java is still very actively developed. Sun Microsystems state that it is goal for them to make Java as good as possible for game development. Sony and Sega are very influential when it comes to games and their presence in the scene promises a bright future for Java gaming. including tutorials. but we have already seen a lot of game related improvements in Java over the last few years. specifications. In fact Java has a strong reputation for being stable and reliable. Most of documentation provided by Sun is freely available on-line so it is easy to get access to needed information. The support for Java is very good: • Both Sun and third party consulting company support is easy to get for the commercial customer. This makes it seem that they are actually keeping their promises. among others. and several other large corporations work very actively on Java and release dozens of new versions of Java related products every month. This is absolutely not the case with Java. actual manuals. will collaborate to build a Java gaming API. • Large communities exist where Java programmers exchange information. A new major release of the Java Development Kit is made approximately every 18 months.2.2.2 Documentation It is often the case that experimental and open source systems are poorly documented making it hard to use them. Neither is true for Java. Mere words are of course not enough. IBM. Fortunately Java is no longer experimental or has few users.3 Continued support Another problem with new systems is that they are not properly supported and/or not actively developed. These are quite good and Sun engineers are often involved in many of them. This statement about high stability and reliability generally only applies to the Sun and IBM implementations of the Java compilers and virtual machines. There are extremely few bugs in Java – fewer than in most C++ compilers – and it has been used for half a decade by millions of people. 8. Much very well written documentation exists for Java. although progress is slower than many had hoped from the outset. This Java gaming API (called the The 76 .1 Reliability and stability It is generally risky to use experimental systems or systems with only few users.8. Other implementations.

In summary. including information about variable names. Certain specialized tools may have to be bought separately such a profilers.javagaming. but also provides a threat to source code security. the cost of per developer seat for Java developers is usually much lower than that for a C++ developer. In fact it is often completely free with regard to software.org.2. Fortunately good solutions exist: 77 . In that same press release Sun also announced the launching of an official Java game development website: www. for instance. 8.2.1 this is why ID software does not use Java. For more information about library wrapping see section 8. If the interface to the C/C++ code is very complex. This includes great development environments (such as Eclipse).5. When Java source code is compiled to byte code. This has discouraged some developers from using Java since they fear that any code they have written may be stolen. Virtually all Java related products needed during the development of a game made in Java is available freely for download. This is certainly not the case with Java. a large amount of information is included. the compilers.2. Read [Melissinos] for more information about the Sun gaming initiative.5 Legacy code If you already have some code written in C or C++ it is vital that this can be used. 8.4 Migration costs Some systems are very expensive to purchase or use. by the Java reflection mechanism. but the complexity of doing so heavily depends on the complexity of the interface of the C/C++ code. This is possible. Byte code can often be decompiled to source code in a way that looks very much like the original including variable and method names. This is needed. None of these require any royalty payment. the Java Game Profile is neither fully specified nor implemented. 8. This is useful in many tools. in Java.6 Code security One drawback of the Java byte code model is that it is quite easy to decompile. automated testing suites and server application development environments. but one might benefit from the commercial ones. As mentioned in section 8. The more complex the interface is the more work is needed to interface it from Java. method names and code structure. and that C/C++ code functionality is vital it may not pay to use Java. at least partially. the virtual machines themselves and the rights to distribute the virtual machines with the game.Java Game Profile [JSR 134]) is being made primarily to allow games to be written for game consoles and other related game devices. Free tools in these areas also exist. At the time of writing.

using InstallShield). The only drawback is that the distribution becomes about 6 MB larger since a virtual machine must be included.• You can use a Java obfuscator. games on Microsoft Windows often need DirectX of a certain version number or higher to be present. I generally recommend the first option. For instance. However. is fully reliable and requires no user intervention. In other words. It usually also reduces the size of the byte code files which is useful if the application is to be downloaded on the Internet. using these approaches will be more complex for the end-user and can only be recommended if absolutely necessary.2. This way the user will never know Java was used and will not have to worry about it. Some new Java developers are often concerned that Java applications in most tutorials are started via the virtual machine (e. Using one of these options.g. These package the application into an exe file for the given platform making its use identical to other applications on the platform. who are used to that they just click on an icon or executable file. Mac and Linux. The user will then have to install this first if needed. and use a launcher program. deployment of Java applications is no longer a problem. 8. code security is not a practical problem. However. Furthermore. these problems can easily be avoided in one of several ways: • If the Java game is to be a regular game application as is usually known from retail it is highly recommended simply to bundle a Java virtual machine with the application. • If you want to avoid including a full virtual machine in the deployment (to make a single distribution more portable or just to reduce the size of the download) you can use Java Webstart or Zero G InstallAnywhere.exe). • Use a Java static compiler. install it as part of the regular game installation (e. to provide an executable file to start it. These allow the same distribution to be used on Windows.7 Deployment For some systems that use virtual machines or other platform specific libraries. This is available for all non-browser based games. 78 .g. the application is an argument to java. deployment can be complicated. you are guaranteed that a virtual machine is present on the computer that it is 100% compatible with your application. These compile the application into machine code and are therefore much harder to decompile. • You can use a Java static compiler. such as the Marner Java Launcher. This makes it hard to start the application for end-users. The purpose of this tool is to mangle the byte code so it cannot be decompiled without reducing performance.

Another popular library is WildTangent. It even supports DirectX 3. called JWindows. It runs very impressively. 79 . Windows or any other library is no exception. Ports exist for IBM AIX. Currently no full and proper DirectX or Windows wrappers are available. WildTangent is a 3D engine for Java (and other languages) wrapping an improved version of the original Genesis3D engine. and SGI IRIX.8. but it still proves that a DirectX based 3D engine can be used from Java. It allows the construction of full Microsoft Windows applications in Java.3 and many of the latest vendor specific extensions. This means that there is no limitation about what system features you have access to from Java. For a full review of WildTangent I recommend [Meigs] 8. itself an object-oriented wrapper around the win32 API. so the normal approach is simply to provide wrappers for those functions needed. The wrapper. It works on any Java 2 compliant virtual machine. Microsoft has written a library they call the Microsoft SDK for Java. This allows the user to write full Windows application in Java. completely circumventing the Java Windows system. Official ports currently exist for Windows and Sun Solaris.0. Inc. for a subset of the Microsoft Foundation Classes (MFC). HP-UX. Linux. The OpenGL implementation of Java3D is generally more bug free and faster than the DirectX implementation. In fact using JNI any library written for use with C or C++ can be used from Java and DirectX. called 6DX for Java.4 Java3D Java3D is an optional package to Java that provides the Java programmer with support for 3D graphics. is somewhat out of date today.0 is old and the Microsoft SDK for Java only works on Microsoft’s own Java virtual machine because it uses special features not in the official Java standard. Java3D is a Java library written using JNI to access OpenGL or DirectX implementations provided by the underlying operating system.3 OpenGL. I have personally written am open source wrapper. It is therefore natural to ask if this is suitable for use with games. OpenGL or the Microsoft Windows SDK. DirectX and other libraries It is a common misconception about Java that it cannot use all sorts of 3rd party libraries such as DirectX. Unfortunately DirectX 3. I have also written an open source wrapper around the DirectX based 3D engine called 6DX from Eldermage Entertainment. A Mac port is supposedly in development. For an instruction to JWindows and more information about wrapping C++ class hierarchies in Java please refer to [Marner00] and [Marner01b]. OpenGL. but I have been unable to get this confirmed. A good open source OpenGL wrapper for Java (usually called OpenGL Java bindings) is available and is called GL4Java from Jausoft supporting OpenGL 1.

Any benchmark should always be taken with a grain of salt. This ensures that is possible to make the game work for as many users as possible. but the result is so clear in this case that is cannot be ignored. nVidia and ATI both has helped to raise the quality of the hardware drivers available. This means that the programmer organizes the 3D scene in a tree structure and then gives the control of the actual rendering to library.5 times slower than the equivalent GL4Java version. so this is not the problem it once was. This is a significant slow down since most of the processing time is supposed to be in 3D hardware. Since much code already has been written it becomes much easier to make a new 3D application and let the library handle the optimizations of the scene graph. This means that user support has no option but tell the user to wait until a new driver is released for his 3D card .10) we saw that the Java3D version (Java3D 1. There are several advantages of using Java3D over lower level APIs: • Significantly reduced development costs. on the other hand. • Built-in support of multiprocessing. Currently only the features in OpenGL 1. If you evaluate Java3D yourself by running some of the demos available with the library please let each demo run for 20-30 seconds or so before judging its performance. This gets harder to do as the scene gets more complex so it is likely that Java3D compares better as this happens. • It often happens that 3D hardware drivers for either DirectX or OpenGL contain bugs. Unfortunately there are also several drawbacks of using Java3D: • Significantly reduced performance compared to highly tuned OpenGL or DirectX applications.Java3D is a scene graph API. This is the time it takes to warm-up the library on an AMD Athlon 700 MHz. This means that many of the latest 3D features are not available to Java3D programmers.3 beta 1 for OpenGL was used) was about 2. When writing 3D applications it is common practice to work around these problems if they should arise. Fortunately.1 and earlier are supported. • Built-in support for VR-helmets and other VR related displays such as CAVES or stereo-vision. The benchmark comparison assumes that the OpenGL application has been organized and tweaked fully for performance.which may take a long time. • Java3D has been made as a common denominator in 3D graphics. Vertex and pixel shaders and other nVidia Geforce 3 features are supposedly planned for Java3D v1. 80 . Java3D is multithreaded internally in order to scale better to multiprocessor systems. • The code becomes 100% portable between DirectX and OpenGL on multiple platforms. This becomes more relevant as the scene grows.4 but this is not scheduled to be released until the spring of 2004 at the earliest. Java3D. Since most OpenGL and DirectX application usually contain some sort of scene graph anyway. In the 3D Demo benchmark (see section 7. is written assuming that the drivers are fully standard compliant. such a scheme is inherently no slower than the underlying API.

Dynamic class reloading allows content to be changed without even stopping the game engine from running. This approach is discussed in depth in [Kreimeier].6 Commercial games using Java An important question when evaluating Java for use in a new project is whether it has been tried before and if so. The best examples of Java in commercial games are games that use dirty Java. Such projects would probably not have the resources anyway to workaround driver bugs or to keep the performance up using one of the lower level APIs such as OpenGL. how it turned out. I have not included any applets in the list on purpose. • Mixed code Java applications (dirty Java). 81 . This is basically games written in C++ that use Java as the general purpose scripting engine. Given the poor performance of Java3D I can not recommend the use of Java3D for games with high requirements in performance. Java mixed with C++ or that use Java as a scripting engine. These mix C++ and Java as needed for the application. is an example of a commercial game using Java3D. i.9 these two approaches are no longer the only viable way of using Java. For instance. Java game applets exist in abundance so there is no doubt that Java can be used in that way. The list is not long. Java has improved. Dynamic class loading allows one to edit the scripts without having to recompile the whole system – this is useful for content control code. Which one to choose and why is discussed in section 8. 8. but are also limited when it comes to system access and performance. As time has passed. • Java as a scripting engine.e. possibly using Java2D and/or Java3D. the 3D engine is written in C++ and all the control code is placed in Java. 8.Roboforge from Liquid Edge Games Ltd. Java is much faster than any custom scripting engine you could write yourself within the project period and it support dynamic class loading and (with a few tricks) dynamic class reloading. These are: • Pure Java application. These games are the most portable and the ones that benefit the most from the high Java programmer productivity. However. if it was there would not be much point in writing this report. games without this requirement will benefit much from the reduced development costs of using Java3D. and as will be discussed in section 8.5 Different ways Java can be used in games When Java is to be used for games there is basically three approaches one can use (we ignore Java applets). You can get a good indication of the capabilities of the library by trying that game.9.

6.1 as its scripting engine. 8. Especially Skies of Arcadia got fine reviews.7 it can be problematic to use Java for console games.2 Mixed code Java (dirty java) Worthy of note in this category is Who wants to be a Millionaire (2000) developed by Jellyvison and based on the popular TV-show of the same name. Unfortunately these two games are not currently available for public viewing or use. This functionality has since then been added to the Java core API in JDK 1. has excellent graphics and gotten fine reviews.6.1 Java as a scripting engine Probably the best example of Java in games is the highly acclaimed game Vampire The Masquerade: Redemption (2000) from Nihilistic software.6 Demo games On QuakeCon 2001 Fullsail Real World Entertainment showed a Quake clone Jamid that they had made. Here the game logic was written in Java and the rest in C++.4 Pure Java applications Commercial games written in pure Java include Shadow Watch (2000).5 Console games As we will discuss in section 8. 82 .com (1998). The rest is written in C++.8.4. Nihilistic Software was very satisfied with the use of Java. none of them got very good reviews. Red Storm Entertainment tried mixing some of the Java code into their product Tom Clancy’s Rainbow Six (1999). According to [Huebner]. Nonetheless. 8. This game uses Java JDK 1. Tom Clancy’s ruthless. Of other commercial games using putting the game logic in Java and the rest in C++ can be mentioned You don't know Jack (1995) by Jellyvision and Majestic (2001) by Electronic Arts.6. according to Chris Melissinos (of Sun Microsystems) the Sega Dreamcast games Skies of Arcadia (2000) and Daytona USA (2001) both by Sega includes a Java virtual machine.6. Roboforge uses native code only to switch to full screen mode. January 2002 Fullsail also made available for viewing the Java3D F1 Gran Prix Demo. It topped the game sales charts for months. but according to [Upton] this turned out not to be an advantage because the complexity of mixing Java and C++ in this case turned out to be too high.3 Almost pure Java The commercial full-price game Roboforge (2001) by In-house is made in Java using Java3D.6. 8. Both were written in Java using Java3D. all from Red Storm Entertainment. and Tom Clancy's Politika (1997). This game runs with very high performance.6. 8. 8.

7. If the Java program uses any native libraries. it is often easy to port various native libraries to multiple platforms if just they where well designed from the start.8. Furthermore. 83 . for instance.3 this is truer for Java than for most languages. This must be so. for which a regular Java virtual machine is available. make it impossible to run Java applications on these machines. This does not.1 Desktop and server computers Automatic portability only holds true under the circumstance that the application is written in 100% pure Java using the Java core API only. In practice this means that if one plans a little an ahead it is quite easily possible to write applications that with a minimal of effort can be ported between Microsoft Windows. This means that it is possible to use the Java gcc front-end gcj. especially if the C/C++ code already was written to be somewhat portable. you write your own Java wrapper for OpenGL then the library code should easily be able to be ported to any platform that supports OpenGL. According to [Patrizio] this is a version of PersonalJava. Although it in theory should be possible to write applications for the consoles using gcj I have been unable to find anybody that has actually done or even attempted to do so. Linux and several other systems. the portability of the program is limited to the platforms for which ports of those libraries exist. Sony Playstation and the Nintendo game consoles no virtual machine is available. Mac. If. including the Microsoft Xbox. if one chooses to use new versions of Java. This often means that if you want to target as broad an audience as possible then you might be better of targeting a bit older version of Java.5 this has been used to make a few commercial games on that platform. 8. As discussed in section 5. ports are only immediately available for Windows. For all the major platforms.2 Dedicated game consoles The only dedicated game console. 8. That said. On most of them a port of gcc is available.7. however. native libraries are written in C or C++ and Java can provide no guarantees that these are fully portable. On the other platforms they only become available when the respective vendors complete their ports.7 Portability One of the arguments for using Java usually is that programs written in Java are fully portable. As mentioned in section 8. including any of Java optional packages such as Java3D.6. a technology related to the Java micro edition for use on handheld computers and other mobile devices. Sun Solaris and Linux. but as we will see in the following sections portability is not automatic. is the Sega Dreamcast.

84 .8 The performance of Java in games Based on the discussions in chapter 6 I will here try to guess the performance consequence of using Java in games in various circumstances.3 the Java Game Profile is a profile for the Java Micro Edition that is to be used to allow Java be used on game consoles. In fact. according to [Kayl] the Java platform will be integrated with the Sony Playstation 2 by the end of 2001. Since Sony is part of this effort we will very likely see this implemented on the Sony Playstation 2. but given the current proposal for The Game Profile. When asked why. This has yet to come to pass but it is likely that it will happen at some point. To summarize. At the time of writing the Java Game Profile specification is not yet complete and the developers are very reluctant to give out details. The Java Game Profile is likely to fix that for the Sony Playstation 2 within a couple of years and probably later appear on the Nintendo consoles. It actually seems quite ironic that it is portability that causes the main problem for Java for use in game development. It appears that both Sun and Sony are aware of this problem and this explains their effort with the Java Game Profile [JSR 134]. I can therefore not recommend relying on it for production code. we must conclude that relying on Java for console development is a risky affair that cannot be recommended. he answered that they had no choice because the Vampire: The Masquerade 2 was to be released on game consoles as well as on desktop computers. At the Game Developers Conference 2001. 8. So except if one develops only for the Sega Dreamcast (which is no longer in production). Microsoft is no longer actively supporting Java (Java is not even included with Internet Explorer in Windows XP) so it is very likely that implementations of the Java Game Profile never will appear on the Xbox. Java can currently not be used on consoles.6.1) said that they would not use Java for Vampire: The Masquerade 2. As mentioned in section 8. The Game Profile will probably never be implemented on the Microsoft Xbox. Since The Java Game Profile has a separate API you should not expect that games written in Java for desktop computer easily can be ported to the consoles when the first implementations of The Game Profile appear. Rob Huebner of Nihilistic software (see section 8. it seems that The Java Game Profile will have a broader appeal than just networks. The motivation mentioned in [Kayl] seems mainly to make it easier to write on-line games for consoles.gcj is an open source effort and in many areas it still appears to be experimental.2. So it will probably be at least a year before we will see the first implementation of it.

I will assume that 40% of the time is used in 3D hardware in C++ and this time is increased with a factor of 2. These numbers should not be taken too seriously. Of the remaining time 10% of the time is used in untweaked code and 90% in tweaked code. the time used in tweaked code.35 + 0. The purpose is to help you to choose the best solution of your project.9 * 1 + 0.25 0.54 * 1.1 * 3.25 Factor slower than C++ only.9 Should I use Java for my next game project? This depends on the project. 1. It is all about choosing the right tool for the right job.54 * 1 + 0.1 * 3.06 * 3. In the calculations here I will assume it is 3. so using Java will almost always be a benefit to a game project.06 * 1 0. But I fully understand that companies hesitate to try out new technologies. For the sake of the calculation is this section I will assume that it is 35%.0 (0% slower) 1. since the exact numbers will depend on a lot of issues and the many assumptions listed above.25 0.35 + 0.35 + 0.9 * 1.4 * 1 + 0. unless some project specific issue 85 . choosing C++ just because that is what game developers are used to do is no valid argument. They should just be seen as guesses that apply to what seems to be a typical project.1 Java provides a clear benefit to productivity. However.25 0. and the time used in untweaked code.1 * 1 0.0 (0% slower) 1.5 if Java3D is used.54 (54% slower) 1.4 * 2.32 (32% slower) The mixed Java/C++ calculations assume that the most often used code is written in C++ and the rest in Java.The time used in a game is the sum of three components: The time used in 3D hardware and the 3D API (for 3D games only). As we saw in section 8.5% slower) 1.5 + 0.924 (92. C++ has proven itself over the years and it therefore a safe bet.006 * 3.225 (22. Scheme used C++ only (no 3D) Pure Java (no 3D) Mixed Java/C++ (no 3D) C++ only (with 3D) Pure Java (with Java3D) Mixed Java/C++ (with 3D) Calculation 0. Untweaked Java code is usually 2.4% slower) 1.4 * 1 + 0. Tweaked Java code is usually 20-50% slower than tweaked C++ code but varies even more in practice.54 * 1. 8.25.5 to 4 times slower than untweaked C++ code.9 * 1 + 0.

Mac OS or Linux. but currently. This can be done as simple as using the GL4Java library (which uses both Java and C++) and then writing all of your application code in Java. Using Java as a scripting language is just a variant of this approach. The game logic is usually those parts of the code that is most buggy so these will benefit much from being written in Java. The most common approach is to use C++ for the low level functionality of the game that is performance critical and which makes much system access (such as the 3D engine) and then write the rest in Java. porting Java games to consoles is not feasible to do. Also. • You already have a large amount of legacy code written in C or C++ that you wish to reuse in the new project. If this is the case you should consider whether it is feasible to mix C/C++ and Java code together in one program. If your legacy code consists of C++ class hierarchies (not simple C-like calls) this interfacing gets much more complex and will probably not be worth the effort.8). as it was summarized in section 8. This is feasible for various strategy games or other games that do not rely on very high performance graphics. Or you can choose to write the performance critical sections of your code in C++ and the rest in Java. This approach can be used even if the overall performance of the game is important since the overall performance impact of using Java in this case will be minimal (see section 8. Issues that make pure Java (including Java3D) unsuitable for a project: • The game relies of high performance to ensure sales. 86 . This is often the case with certain genres of games.causes Java prevents it from being used.8 the use of Java is very likely to have a negative impact on performance. If you decide to use Java in your project you should what approach to take by considering your needs: First consider building the game in pure Java (including any optional packages) since this will give the highest productivity gain. for instance. first person shooters. Using pure Java has the added benefit of being cross platform which is good if you later need to port it to. Assuming that the legacy code has a clear interface it will be easy to interface from Java. This will allow you to combine the high productivity of Java with the high speed of C++. The only issue that currently prevents the use of any Java at all is that the game needs to be ported to game consoles. In that case you are probably better of either ditching the legacy code or Java completely. The Java Game Profile may change this in the future. say. This way you can reuse any legacy code you may already have in C/C++. If pure Java is not suitable for your project you should next consider mixing Java and C++.

Java is much slower than C++. however. Java runs much more efficient than the vast majority custom scripting languages available. as a general purpose scripting language for the control code. Java can only be used for games that do not need to be ported to consoles and only partially for games that rely on maximum performance to ensure sales.9 Conclusion In contrary to popular belief. This productivity increase makes Java a good choice for low-profile games and for high profile games in genres that do not rely on maximum performance to ensure sales. With the development of the Java Game Profile. often a factor of three or four. A rough estimate based on the various benchmarks would say that tweaked Java code is a little slower than C++. typically 20-50% on the average. Java increases the overall productivity of a software project with about 30% and the productivity of the code phase with about 65%. and the still improving speed of Java virtual machines. Because of the relatively lower performance of Java. The slowdown is less in 3D applications. This is quite a significant increase. I cannot recommend pure Java for highly performance critical games. However. the use of Java will increase productivity and thereby allow a better game to be produced for the same money. It is especially good for low-profile games since the cost of Java tools and libraries are significantly lower than those for C++. For high profile games that do not need maximum performance. it is likely that the viability of Java for game development will improve in the future. still recommended that Java is used for at least parts of the game. as it stands now. It is. but this is hard to tell for certain because of the large variations in the speed seen in the benchmarks. I consider the current ones highly risky. This makes it vital to tweak the performance critical sections of Java code. If the game is to be released on consoles then the use of Java for any part of the game cannot be recommended. The biggest problem at the moment with using Java for game development is that proper ports do not exist for game consoles. for instance. For untweaked code. where performance mostly depends on the performance of the 3D hardware and not on the programming language used. 87 . Java applications are in fact not much slower than C++ applications when they have been tuned for performance. the rapidly improving development tools for Java.

html [DFC 2] DFC Intelligence: Top Ten Myths of the Interactive Entertainment Industry.dfcint. K.Java to overtake C/C++ in 2002.: Study .: Refactoring – Improving the Design of Existing Code.ce.chalmers. Available only on-line at: http://zdnet. B: Thinking in Java. E. [Gardner71] Gardner. Scientific American. Market for Video Games & Interactive Electronic Entertainment. try finding the referred pages elsewhere by searching for them on Google. Kindberg. Opdyke. U. p.The fantastic combinations of John Conway's new solitaire game "life". de. Addison-Wesley. van. so many of the provided URLs will likely stop working within few months after this report has been published. Krevald. the Garden of Eden and the game "life". R.se/staff/uffe/Vfc_Bbox. : Deisgn Patterns – Elements of Reusable Object-Oriented Software.: Distributed Systems – Concepts and Design. Overmars.com/j2se/1. Prentice-Hall. Excerpt from U. Brant.mindview. 1998. Available only online at: http://www. 1994. Available on-line at: http://www.4/docs/guide/lang/assert. R.Appendix A: References Note: The Internet is an evolving place. D.com/features/1998/07/efficiency.4 documentation: Assertion Facility. Chalmers University of Technology. [Gardner70] Gardner. 88 .net/Books/TIJ/ [Fowler] Fowler. Available on-line at: http://www.com/game_article/statepcmarket99. & Möller. [Assarsson] Assarsson. & Roberts. 2000. J.net/Books/TIJ/javabook. 2000. 2001..com/2100-1104-503983. 112-117. Department of Computer Engineering. Helm.com.mindview. Prentice-Hall. [Byous] Byous. M. Addison-Wesley. 2001-2002. 5(1):9-22.pdf [Assert] The JDK 1. 1999.. P. M: Mathematical Games . G. 1998. Available only on-line at: http://java. [Eckel00] Eckel. J. 120-123.html [Berg] Berg.D. J. W. M. 1st Edition. Second Edition.On cellular automata. Journal of graphics tools. 2nd Edition.html.com/game_article/myths.html?legacy=zdnn&chkpt=zdhpnews01 [Gamma] Gamma.dfcint.. 3rd Edition. Available online at http://www. Addison-Wesley. Dollimore. selfreproduction. [Galli] Ghalli. M: Mathematical Games . T. If this happens. 2000. p.sun. M. October 1970. Johnson.: Computational Geometry. M. O. T. & Vlissides. [DFC 1] DFC Intelligence: The state of the PC game market 1999. Scientific American.html [Coulouris] Coulouris.html [Eckel98] Eckel. February 1971. Available online at http://www. J. SpringerVerlag. Available only on-line at: http://java.: A Jolt of Efficiency. B: Thinking in Java. & Schwarzkopf. Beck.sun. Algorithms and Applications.: Optimized View Frustum Culling Algorithms for Bounding Boxes.

& Yellin. S. 1988.com/features/20000802/huebner_01.B.sun.: Postmortem of Nihilistic Software’s Vampire: The Masquerade – Redemption. Joy.com/features/2001/06/sony.dk/ [Marner01b] Marner.sun. Available only on-line at: http://jcp.rolemaker. 89 .html [Gosling96] Gosling. 1999. Available on-line at: http://java.zip [Marner01] Marner.dk/articles/Wrappers/Report.jsp [JSR 14] Java Specification Request (JSR) 14: Add Generic Types to the Java programming language.rolemaker. Available only online at: http://java. R. Available on-line at http://www. 2nd Edition.sun.org/jsr/detail/014. 2000.Sony Computer Entertainment Integrates JavaTM Technology Capabilities into PlayStation 2. Also available online at: http://www. Available only on-line at: http://java.com/products/hotspot/whitepaper. Available on-line at: http://www.com/docs/white/ [Hotspot] The Java Hotspot Performance Engine Architecture. J. & Bracha. T. J.org/jsr/detail/134. 1991. [Kreimeier] Kreimeier.sun.html?frontpage-headlinesfeatures [Kernighan] Kernighan. J. G. B.: Role Maker – A Computer Roleplaying Game Authoring System. Addison-Wesley.jsp [Kayl] Kayl.. JavaOne 2001. J. 2nd Edition.dk/articles/Wrappers/JavaWrapperHowTo.[Gosling00] Gosling. 2001.: How to write a Java wrapper for a C++ class library. 2nd Edition. Available on-line at: http://www.com/javaone/javaone2001/pdfs/2647. 2001.: The Java Language Specification.W.: Providing Java wrapper classes for C++ class hierarchies.com/features/19990611/java_01. July 2000.gamasutra. Available only on-line at: http://jcp. F. : The Evolution of Performance on the Java Platform “A Panel Discussion”.sun. : The Java language – An overview.0. K. Available only on-line at: http://www.rolemaker. Available on-line at http://java. 2000.: The C Programming Language. J.zip [Meigs] Meigs. June 2001. 2nd Edition.pdf [JSR 134] Java Specification Requests (JSR) 134: The Java Game Profile. 2001. G.M.: C++ Primer.: The Java Virtual Machine Specification. July 1999.gamasutra. Game Developer Magazine.: Dirty Java – Using the Java Native Interface within Games. Game Developer Magazine. Steele. [Marner00] Marner.com/docs/books/jls/index. 2001. Available on-line at: http://java. & Ritchie D. B.com/docs/books/vmspec/ [Lippman] Lippman. Addison-Wesley. Game Developer Magazine. White Paper.sun. T. B. Prentice Hall. Available on-line at: http://java.html [Huebner] Huebner.htm [Lindholm] Lindholm.: PLAYING TO MILLIONS . R.htm [Hutcherson] Hutcherson.: Product Reviews: WildTangent’s Web Driver 2. 1999-2001. Addison-Wesley.

Available on-line at: http://developer. & Healy M. J. Available only on-line at: http://www. B. Available only online at: http://www.html [Phipps] Phipps.org/ipLinks/java/designPatterns/enum/index.com/features/20000121/upton_01.sun. Available only on-line at: http://java.html [Photon] Photon: Low latency garbage collection via reference counting.wellscs. M.: Electronic Gaming Industry Chooses Java Technology as Open Software Development Platform for Next Generation Entertainment Services. 1997. 41. 2000.: Postmortem: Read Storm’s Rainbow Six. B. C. Available on-line at: http://www. [Upton] Upton. 1997.: Java Live Interview: C++ vs. B. 6. 2000.avault.com/smi/Press/sunflash/200103/sunflash.sun. 3rd Edition.com/developer/getarticle. [Stroustrup] Stroustrup.: PC gaming as an industry. 6.gamasutra. 1999.com/developer/community/chat/JavaLive/1997/jl0812. Java. 2000. Pages 38 – 42.F: Fundamental Design Patterns in Java – The “Enum” (Proto-)pattern. Available on-line at: http://www.html [Singal] Singal.wired.com/ [Shuster] Shuster. AddisonWesley. Sun Brings Enterprise Expertise to The Electronic Gaming Industry.veronissuhler. & Nguyen.htm 90 .: The C++ Programming Language. 2000. P.com/software/cover/2002-0104/ [Meurrens] Meurrens. Communications of the ACM 41.asp?name=bwardell1&page=1 [Wardell01] Wardell.sun. O’Reilly & Associates. 2000. Available only on-line at: http://www. Available only on-line at: http://www.com/features/2000/06/time-line.avault.com/robert/java/productivity. [Sun] Sun Microsystems: The Java Platform: Five Years in Review.sun. 1998-1999.net/reference/programming/features/llgc/ [Shirazi] Shirazi.htm [Veronis] Veronis Suhler Media Merchant Bank: Entertainment Industry Segment Performance. B.java.W. May 1999.: Java offers increased productivity.gamedev. A. Also available on-line at: http://www. See also: http://www. Pages 34 – 37.html [Patrizio] Patrizio.[Melissinos] Melissinos. R.com/developer/getarticle.1284.javaperformancetuning.meurrens. M. M.html [Wardell00] Wardell. S. Game Developer Magazine. B.: Playing for Keeps.: Dreamcast Gets Java Jolt.1.: PC gaming as an industry Part IV – Destroying Myths. Available only on-line at: http://www.00. Available only on-line at: http://www.36810.: The Java Factor. 2002. G.com/filmed/segment. 2000.: Java Performance Tuning. (Jun 1998). 2001.asp?name=bwardell4 [Wells] Wells. Available only on-line at: http://www.com/news/culture/0. (Jun 1998).: Why Are We Using Java Again? Communications of the ACM.html [Tyma] Tyma.20010322.

com/docs/books/performance/1st_edition/html/JPTitle. Available on-line at: http://java. 2000. S.html 91 .fm.[Wilson] Wilson. & Kesselman. Addison-Wesley.sun. J.: Java Platform Performance – Strategies and Tactics.

h ////////////////////////////////////////////////////////////////////////////// // Untweaked particles // by Jacob Marner. 0 not included.h A set of constants for use in the program A set of generic functions that is useful for multiple purposes. The main part of the program.h> // The gravitation constant used between particles.h World. // // A set of constants for use in the program // ////////////////////////////////////////////////////////////////////////////// #ifndef CONSTANTS_MODULE #define CONSTANTS_MODULE #include <limits.67390*pow(10.h Main.Appendix B: Untweaked Particles Source code Part 1: C++ source code The source code consists of the following files: constants.cpp Constants. The world within which the particles exist.cpp Vector. -11)). const float GRAVITY = (float)(6. The header to world. const float MASS_PER_VOLUME = 50000. // When particles are generated by random their mass // is set to a random number between 0 and // MAXRANDOMMASS. High 92 . const float MAXRANDOMMASS = 10000000. // A high value makes the size of particles smaller // given the same mass. // When particles are generated by random their speed // along each axis will be evenly distributed among // -MAX_RANDOM_SPEED and MAX_RANDOM_SPEED const float MAXRANDOMSPEED = 0.h general.h> #include <math.cpp World. Feb 2002. A generic 3D vector class of floats. // When particles are generated the world is given a // size to achieve constant particle density. // The ratio between mass and volume of particles.

h ////////////////////////////////////////////////////////////////////////////// // Untweaked particles // by Jacob Marner.// number gives more space. If label was not found return defaultvalue. } } return false. inline int randomInteger(int low.h> #include <windows. } // Search a set of command line arguments for one beginning with label. } int num = (int)((((double)rand())/(((double)RAND_MAX)+1.0)) * (high-low+1) + low). low = high. Feb 2002.i++) { if (!strncmp(label. int argc.strlen(label))) { return true. inline int getIntegerArgument(const char* label. assert(num>=low).argv[i].h> <string. high = temp.h> // Return the absolute value of a template <class Type> Type Abs(const Type a) { return a<0 ? -a : a. } // Search a set of command line arguments for one beginning with label. #endif general. int argc. const float PARTICLEDENSITY = 250000. char* argv[]) { for(int i=0. int defaultvalue) { for(int i=0. both inclusive. int high) { if (low>high) { int temp = low.i<argc.h> <stdlib. // If it exist return true. // If found parse the ramainder of the string as an integer and return // the value.i<argc. assert(num<=high).h> <stdio.i++) { 93 . char* argv[]. } // Return a random int number between low and high. inline bool getBooleanFlag(const char* label. return num. // // A set of generic funtions that is useful for multiple purposes // ////////////////////////////////////////////////////////////////////////////// #ifndef GENERAL_LIB #define GENERAL_LIB #include #include #include #include <assert.

100).i++) { if (!strncmp(label. Feb 2002.i<argc.strlen(label))) { char* s = argv[i]+strlen(label). } // Search a set of command line arguments for one beginning with label.h" "Vector.argv. // -steps : The number of steps to compute. ////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include "constants. inline float getFloatArgument(const char* label.argv).h" "world. // If found parse the ramainder of the string as a float and return // the value.argv[i].cpp ////////////////////////////////////////////////////////////////////////////// // Untweaked particles // by Jacob Marner. 94 .if (!strncmp(label.argv.argc.argv[i]. char* argv[]. } } return defaultvalue. // -h: The amount of time that passes for each step.h> <float. int numsteps = getIntegerArgument("-steps:". int main(int argc. char* argv[]) { // Read command line arguments bool showprogress = getBooleanFlag("-showprogress".h> <iostream> typedef list<Particle> ParticleList. // // The Main class. This is where the timed calculation occurs. Use a negative // value to cause it to be autocomputed.argc.argc.h" <list> <stdio. float optionh = getFloatArgument("-h:". float defaultvalue) { for(int i=0. } } return defaultvalue.strlen(label))) { return atoi(argv[i]+strlen(label)).&f). // Particles that go out of bounds will be deleted. return f. float f.100). Defaults to 100."%f". Defaults to autocompution. if (numsteps<0) numsteps=0. int argc.h" "general.argv. } #endif main. sscanf(s.-1). If label was not found return defaultvalue. int numparticles = getIntegerArgument("-particles:". // -particles: The number of particles in the world to begin with. // // Takes these parameters: // -showprogress : Prints a dot for each step computed.argc.

initializeFirstStep(optionh).) for (other = self.0)).isPointIn(self->x)) { self = particles.i++) { world.&v)!=0.worldsize). If so: delete.h * 2. } } } for (self = particles. self!=particles.resetTraversal().addRandomParticle().0) { particles.argv.argc. Vector x. cout << "h: " << world.v)).h << endl.end(). if ((self->x . } // Do collision detection. self!=particles. than self.push_back(Particle(mass. other!=particles.getSquaredLength() <= radiussum * radiussum) 95 . ++other) { if (self != other) { Vector tmp = self->F(*other)*(twoh). step < numsteps. float worldsize = getFloatArgument("-worldsize:".numparticles).) { // Compute new x value self->x += self->v * (twoh).end(). ParticleList::iterator other. for (self = particles. // Create particlelist from world ParticleList particles.begin(). step++) { // Compute one step in the leap-frog method ParticleList::iterator self.if (numparticles<0) numparticles=0.begin(). world. while (world. } // Do the steps in the simulation float twoh = world.x.1. clock_t starttime = clock().end(). ++self) { for (other = self. World world(worldsize. other->v += -tmp / other->getW(). self->v += tmp / self->getW(). } world.worldsize.0/3.i<numparticles.end(). for (self = particles.begin(). pow(PARTICLEDENSITY * max(1. // Detect if particle is out of the world bounds.other->x).v.getNextParticle(&mass. self!=particles.) { if (self != other) { float radiussum = self->getRadius() + other->getRadius().erase(self).++self) { // For particels placed later in the list. float mass.end(). other!=particles. if (!world. } else ++self. for (int i=0. for (int step = 0. // (The once before has already been computed.&x.

y(y).it->v). float y. } float timeelapsed = ((float)(clock() ." << endl. it!=particles.erase(other)." << endl.h> "constants. float weightsum = self->getW() + other->getW().z) {} inline Vector& operator+=(const Vector& v) 96 . z(0) {} Vector(const float x. self->v = (self->v * self->getW() + other->v * other->getW())/(weightsum). } else ++other. z(v. y(0).it->x.addParticle(it->getW(). } } if (showprogress) cout << ".h" <stddef. } Vector. } else ++other. // // A generic 3D vector class of floats // ////////////////////////////////////////////////////////////////////////////// #ifndef VECTOR_CLASS #define VECTOR_CLASS #include #include #include #include "general. self->x = (self->x * self->getW() + other->x * other->getW())/(weightsum). Vector(void) : x(0). cout << "Elapsed time: " << timeelapsed << " sec. float z.{ // Collision detected: Combine the two particles in one.y). const float z) : x(x)." << flush.x). if (numsteps!=0) cout << "Time per step: " << timeelapsed / numsteps << " sec. self->setW(weightsum). // Delete the particle pointed to by other.begin(). if (showprogress) cout << endl. world.starttime)) / CLOCKS_PER_SEC.clear(). other = particles. Feb 2002.++it) { world.h ////////////////////////////////////////////////////////////////////////////// // Untweaked particles // by Jacob Marner. y(v.h" <math. } return 0. cout << "Steps performed: " << numsteps << endl.end().h> class Vector { public: float x. const float y. for (ParticleList::iterator it = particles. z(z) {} Vector(const Vector& v) : x(v.

y.z).x. } inline Vector& operator-=(const Vector& v) { x -= v.y.x.z-v. return *this.y/c.-z).z. } 97 .z/c). } inline Vector& operator*=(const float c) { x *= c.y-v. y -= v. z -= v. y *= c.-y. y /= c. return *this.z+v. } inline Vector operator-(void) const { return Vector(-x.y. } // Copy vector inline Vector& operator=(const Vector& v) { x = v. return *this.x. return *this.y+v.x. z = v. z /= c.y. return *this. } inline Vector operator/(const float c) const { return Vector(x/c.x.z. y += v. } inline Vector operator*(const float c) const { return Vector(x*c.z.y. } inline Vector& operator/=(const float c) { x /= c. z += v.z). } inline Vector operator-(const Vector& v) const { return Vector(x-v. } // Get 2-norm inline float getLength(void) const { return (float)sqrt(x*x+y*y+z*z).y*c. } inline Vector operator+(const Vector& v) const { return Vector(x+v.{ x += v. y = v. z *= c.z*c).

x).x*v. z = -z.y-y*v.y && z >= lowerCorner.z-z*v. y /= l.y). return *this. } inline Vector inverted(void) const { return Vector(-x.z && x < upperCorner. z /= l. } }. y / l.x && y >= lowerCorner. #endif World.y. // 98 . } // Return true if this vector is a position between the lower and upper // corner given of a rectangle. } inline Vector& normalize(void) { float l = getLength().z.x-x*v.cpp ////////////////////////////////////////////////////////////////////////////// // Untweaked particles // by Jacob Marner.Abs(z)).-y. } // Return a normalized version (unit length) of the // vector. y = -y. inline Vector normalized(void) const { float l = getLength(). } // Get 2-norm but before the square root is taken inline float getSquaredLength(void) const { return x*x+y*y+z*z. Each coordinate of lower must be lower // than the same of that of upper.z). } inline void invert(void) { x = -x.Abs(y)).-z).// Get max of each element (not the same as infinity norm) inline float getMax(void) const { return max(max(x.z). x /= l. return Vector(x / l.z*v. inline bool isInRectangle(const Vector& lowerCorner. const Vector& upperCorner) const { return (x >= lowerCorner. z / l). Feb 2002. } inline float getInfinityNorm(void) const { return max(max(Abs(x). } inline Vector cross(const Vector& v) const { return Vector(y*v.y && z < upperCorner.x && y < upperCorner.

Vector* v) { if (it == particleList. *v = b.end()) { *mass = 0. float World::getRandomfloat(float maxValue) { // Find float in interval 0 <= r <= maxValue float r = ((float)rand())/(((float)((float)RAND_MAX))+1.clear().0.0). float World::getNextParticle(float* mass. void World::clear(void) { particleList.0).// The world within which the particles exist.getRandomfloat(maxz)). 99 .v. Vector speed(getRandomfloat(MAXRANDOMSPEED*2)-MAXRANDOMSPEED. } // Enumerate the set of particles/bodies. *x = b.x. } // Call this before enumerating using getNextParticle() void World::resetTraversal(void) { it = particleList.h> <float. *mass = b. const Vector& x. void World::addRandomParticle(void) { Vector pos(getRandomfloat(maxx). } else { Particle b = *it.x. Vector* x. assert(r>=0.v)). return 0. *v = Vector(0. } } // Generate a random number between 0 and maxValue.getW(). return *mass.getRandomfloat(maxy).h> <iostream> <limits. Call resetTraversal // first. } // Add new particle to the world. getRandomfloat(MAXRANDOMSPEED*2)-MAXRANDOMSPEED). srand(time(NULL)).push_back(Particle(mass.h> // Delete all particles in the world. // ////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include "world. return r*maxValue. const Vector& v) { particleList.begin().0). assert(r<=1.0). } // Generate a random particle/particle and add it to the world. getRandomfloat(MAXRANDOMSPEED*2)-MAXRANDOMSPEED. ++it. void World::addParticle(float mass. *x = Vector(0.h> <assert.0.h" <stdio.0).

speed void World::print(void) { for (ParticleList::iterator it = particleList.x.begin(). pos.z << endl. void World::initializeLeapFrog() { // Note: There is no need to write v to temporary variable and then // commit changes since F() does not use x.speed)). cout << b.y << " " << b.getW() << " " << b.end().x << " " << b. Giving a negative number will cause h to // be approximated automatically. const Vector& xo) { Vector v. self!=particleList. const Vector& vn. const Vector& xn. } // Print particle data to screen. Fopr each particle: Weight.x. ++it) { Particle b = *it. 100 .begin().end(). } else { h = optionh.particleList. ParticleList::iterator other.x.0) { // If the user did not set h explicitly or set it to // a negative value then approximate it. position and direction that is // orbiting around the other given mass and orbit.pos.v.v. if (h==-1.y << " " << b. } } } // Add a particle with a given mass. ++self) { for (other = particleList. it!=particleList. Pass the h option given // on the command line. void World::addOrbitalParticle(float orbitMass.push_back(Particle(getRandomfloat(MAXRANDOMMASS). float centerMass.v. Vector normal = xn-xo. if (optionh<=0) { h = 1.begin(). ++other) { if (self!=other) self->v += self->F(*other)*(h/self->getW()). We do not update x // in this first step since we can just use x0. ParticleList::iterator self. } } // Call this to initialize h if needed. other!=particleList.end(). for (self = particleList. } } // Compute v_1 to start leap frog method. void World::initializeFirstStep(float optionh) { // Only initialize if it has not already been // done.z << " " << b. // Set direction of v by projecting v onto orbit plane.x << " " << b. } initializeLeapFrog().

} inline float getRadius(void) { return radius. // Set length of v v. } inline void setW(float mass) { w = mass. v *= sqrt(GRAVITY * centerMass / normal. class Particle { float w. xn. const Vector& v) : w(w).h" <list> <ostream> <stdio.cross(normal). } Particle(float w. v = crossed.0/3.cross(vn). // Position Vector v.h" "Vector. using namespace std. } World. // ////////////////////////////////////////////////////////////////////////////// #ifndef WORLD_MODULE #define WORLD_MODULE #include #include #include #include #include #include #include "constants. 101 . const Vector& x. public: Vector x. // // The world within which the particles exist.h> <string> "time.h ////////////////////////////////////////////////////////////////////////////// // Untweaked particles // by Jacob Marner.h" // Retrieve STL classes into default namespace.getLength()). x(x). addParticle(orbitMass.normalize(). updateRadius().Vector crossed = normal. } float radius. Feb 2002.1. // Move vector inline float getW(void) { return w.0). // Mass inline void updateRadius() { radius = (float)pow(w/MASS_PER_VOLUME. v). v(v) { updateRadius().

// Iterator for traversal ParticleList::iterator it. const Vector& x. // Start a traversal of the bodies void resetTraversal(void). 102 . // Add a new Particle to the world such that it will orbit around a Particle // given by the second set of coordinates. float maxz. void addOrbitalParticle(float orbitMass. // Return random float in range 0 < x <= maxValue.getSquaredLength(). } // Add a random Particle to the world. const Vector& v1. it = particleList. void initializeLeapFrog(void). } }. void addRandomBodies(int count). float maxz) : h(-1).x.begin(). World(float maxx. // Delete all bodies in this world. public: // Stepsize float h. return -(diff*(w*otherw*GRAVITY/(slength*sqrt((float)slength)))). void clear(void). float centerMass. // Legal range are 0 <= max<a> < maxValue float maxx. } inline Vector F(const Particle& other) { return F(other.} // Calculate and return force applied between otherx and otherw inline Vector F(const Vector& otherx. const Vector& x2). float maxy. The speed given will // be project down to orbital plane and the speed // may change. class World { private: ParticleList particleList. const Vector& x1. // Create a new world with the given bounderies. void addRandomParticle(void). maxy(maxy). float otherw) { Vector diff(x . maxz(maxz) { srand(time(NULL)).otherx). float slength = diff. const Vector& v).other. // Add many random bodies to the world. // Performs the first step of the leap frog algorithm. float getRandomfloat(float maxValue).w). // Add a Particle to this world with the given mass and speed. typedef list<Particle> ParticleList. float maxy. void addParticle(float mass. maxx(maxx).

} // Assuming that the initialization has not yet occured // for this world (if it has then nothing happens) then // this will give h a value and then perform the first step // of the leapfrog algorithm. Vector* v). // Is printed in the form: "mass position speed" for // each line.z >= 0 && p. // Returns true if the given vector falls within // the world bounderies.e. void initializeFirstStep(float optionh).x >= 0 && p. } // Print the bodies to the standard output stream.maxy. // Return the number of bodies in this world inline int getSize() { return particleList. void print(void). h was set from the command line) use that h.maxz).z < maxz). Vector* x. If optionh is set to a positive // value (i.// Get the next Particle.y < maxy && p.y >= 0 && p. inline bool isPointIn(const Vector& p) { return (p. } }. Vector getWorldExtent(void) { return Vector(maxx. #endif 103 . float getNextParticle(float* mass.size(). // otherwise calculate approximation.x < maxx && p. Returns 0 when done. // Return the size of the world.

Part 2: Java source code The source code consists of the following files: Arguments.java This file is used when parsing command line arguments A set of constants defining the behavior of the particle simulator.java World.java Iterator. // // This file is used when parsing command line arguments // ////////////////////////////////////////////////////////////////////////////// public class Arguments { String[] args. Arguments. i < args. The world within which the particles exist.length. An iterator for a singly linked list classA singly linked list class The Main class. } public int getIntegerArgument(String label. public Arguments(String[] args) { this.java ////////////////////////////////////////////////////////////////////////////// // Untweaked particles // by Jacob Marner. int defaultvalue) { for (int i = 0.equals(label)) { return true. i < args.java Main.java Vector. i++) { 104 . i++) { if (args[i].args = args.java List. Feb 2002. } public boolean getBooleanFlag(String label) { for (int i = 0. This is where the timed calculation occurs.java Particle.java Constants. } } return false. Objects of this class represents a single particle A generic 3D vector class of floats.length.

pow(10.startsWith(label)) { String s = args[i].substring(label. try { return Integer. // A high value makes the size of particles smaller // given the same mass.parseFloat(s). // When particles are generated by random their mass // is set to a random number between 0 and // MAXRANDOMMASS.parseInt(s). } public float getFloatArgument(String label. 0 not included. -11)). try { return Float. // The ratio between mass and volume of particles. i++) { if (args[i]. } } } return defaultvalue. } catch (NumberFormatException e) { return defaultvalue.startsWith(label)) { String s = args[i].if (args[i]. // ////////////////////////////////////////////////////////////////////////////// public class Constants { public static final float GRAVITY = (float)(6.substring(label. public static final float MASS_PER_VOLUME = 50000. High 105 . } catch (NumberFormatException e) { return defaultvalue. Feb 2002. float defaultvalue) { for (int i = 0. i < args.length()).67390*Math.length. // When particles are generated the world is given a // size to achieve constant particle density. public static final float MAXRANDOMMASS = 10000000. } } } return defaultvalue.java ////////////////////////////////////////////////////////////////////////////// // Untweaked particles // by Jacob Marner. // When particles are generated by random their speed // along each axis will be evenly distributed among // -MAX_RANDOM_SPEED and MAX_RANDOM_SPEED public static final float MAXRANDOMSPEED = 0. } } Constants.length()). // // A set constants defining the behavior of // particle simulator.

transient List. public final class Iterator implements java.mySList. myNode = iterator. } public Iterator(Iterator iterator) { mySList = iterator. Feb 2002. List.util.// number gives more space. } public Object clone() { return new Iterator(this). public Iterator() { } Iterator(List list. } public boolean atBegin() { return myNode == mySList.java ////////////////////////////////////////////////////////////////////////////// // Untweaked particles // by Jacob Marner. // ////////////////////////////////////////////////////////////////////////////// import java. public static final float PARTICLEDENSITY = 250000.next. return this.ListNode node) { mySList = list.IOException. } public boolean atEnd() { return myNode == null.io. } public void advance() { myNode = myNode.myNode. // // An iterator for a singly linked list class.Enumeration { List mySList. } Iterator. } public boolean hasMoreElements() { return myNode != null.ListNode myNode. myNode = iterator. } public Object nextElement() 106 . } public Iterator assign(Iterator iterator) { mySList = iterator.myNode. myNode = node.head.mySList.

head). public List() { } public Enumeration elements() { return new Iterator(this. public class List { ListNode head.object.Enumeration. } public int size() { return length. head).io.IOException.util. } public void clear() { 107 . } catch (NullPointerException ex) { throw new java.NoSuchElementException("SListIterator"). Feb 2002. null). // // A singly linked list class. } public Iterator end() { return new Iterator(this.object. // ////////////////////////////////////////////////////////////////////////////// import java. int length. } public boolean isEmpty() { return length == 0.util. return object. } } List. } public Iterator begin() { return new Iterator(this. ListNode tail. import java.{ try { Object object = myNode.java ////////////////////////////////////////////////////////////////////////////// // Untweaked particles // by Jacob Marner.next. } } public Object get() { return myNode. myNode = myNode.

node = node. node. node. node != tail. } public Object add(Object object) { ListNode node = new ListNode(). if (--length == 0) tail = null. } public void pushFront(Object object) { ListNode node = new ListNode(). } public Object popBack() { if (length == 0) throw new java. else tail.object.head = null. return r.next = null.next = head. } public Object popFront() { if (length == 0) throw new Error("Slist is empty").object. --length. tail = null. node.next = node. return r. if (previous == null) { r = head. for (ListNode node = head.object = object.Error("SList is empty"). } tail = previous. previous.next.next) previous = node. if (++length == 1) head = node.object = object. Object r = head.next. } 108 . return null. head = null. Object r. } else { r = previous. tail = node. length = 0. ListNode previous = null.lang.object. if (++length == 1) tail = node. head = node. head = head.

mySList != this) throw new IllegalArgumentException("Enumeration not for this List"). This is where the timed calculation occurs. Defaults to autocompution. // // Takes these parameters: // -showprogress : Prints a dot for each step computed. } final static class ListNode { public ListNode next = null. if (previous == null) head = target. Object retval = ((Iterator) pos). } private Object remove(ListNode target) { ListNode previous = null. 109 .next. // -particles: The number of particles in the world to begin with. Feb 2002. Defaults to 100.get(). return retval. if (target == tail) tail = previous.*.next = target.myNode). return target. // -h: The amount of time that passes for each step. } public Object remove(Enumeration pos) { if (!(pos instanceof Iterator)) throw new IllegalArgumentException("Enumeration not a Iterator"). // // The Main class. } } Main. ////////////////////////////////////////////////////////////////////////////// import java. Use a negative // value to cause it to be autocomputed.next. --length. node = node. if (((Iterator) pos). public Object object = null. remove(((Iterator) pos). // -steps : The number of steps to compute. // Particles that go out of bounds will be deleted.object.util.next) previous = node. else previous.java ////////////////////////////////////////////////////////////////////////////// // Untweaked particles // by Jacob Marner. public class Main { public static void main(String[] args) { Arguments arguments = new Arguments(args). for (ListNode node = head. node != target.public void pushBack(Object object) { add(object).

int numparticles = arguments.get().h).0)).addRandomBody().clone(). if (numparticles < 0) numparticles = 0.) for (other = (Iterator) self. for (self = particles. int numsteps = arguments. float worldsize = arguments. } } } for (self = particles.h * 2. i < numparticles. while ((b = world.multiply(twoh). step++) { // Compute one step in the leap-frog method Iterator self. 1. world. !other. step < numsteps. than self.getIntegerArgument("-particles:". // (The once before has already been computed.v.pow( Constants.divide(bself.0 / 3.getW())).h + world. for (int step = 0. } world.subtracted(tmp.F(bother).atEnd().added(tmp.out.v.max(1. bself.pushBack(new Particle(b)).0f */ world. World world = new World(worldsize.get().added(bself. worldsize).boolean showprogress = arguments.PARTICLEDENSITY * Math. self. // Create particlelist from world List particles = new List(). float optionh = arguments.advance()) { Particle bself = (Particle) self. other. worldsize.getNextBody()) != null) { particles.h. // For particels placed later in the list. // Detect if particle is out of the world bounds. } // Do the steps in the simulation float twoh = /* world.v.get(). bother. if (numsteps < 0) numsteps = 0. numparticles).isPointIn(bself.getW())). !self.advance()) { Particle bother = (Particle) other.begin(). If so: delete.resetTraversal(). Iterator other. -1).divide(bother. 100). !self. if (bself != bother) { Vector tmp = bself.) { Particle bself = (Particle) self.println("h: " + world. (float) Math. // Compute new x value bself. 100).getIntegerArgument("-steps:".atEnd(). i++) { world.x)) 110 .getFloatArgument( "-worldsize:".x. Date starttime = new Date().begin().getFloatArgument("-h:".multiply(twoh)).atEnd(). if (!world.initializeFirstStep(optionh). for (int i = 0. Particle b.getBooleanFlag("-showprogress"). System.

Iterator tmp = (Iterator) other. self.x. } else other. self. System.setW(weightsum). if (showprogress) System. !self.flush(). particles.advance().multiply(bother. } } Particle.advance().subtract(bother. } else self. System.{ System.remove(tmp).advance()) { Particle bself = (Particle) self.clone().getTime())) / 1000.multiply(bother.y + " " + bself. for (self = particles.atEnd().v = bself.add(bother.v. particles.getW().java ////////////////////////////////////////////////////////////////////////////// // Untweaked particles // by Jacob Marner. // // Objects of this class represents a single particle // ////////////////////////////////////////////////////////////////////////////// 111 .get().advance().x.starttime. } else other. if (numsteps != 0) System. Iterator tmp = (Iterator) self.x.getSquaredLength() <= radiussum * radiussum) { // Collision detected: Combine the two particles in one.getW())).getW() + bother. } } if (showprogress) { System.advance(). // Delete the particle pointed to by other.println().out.remove(tmp).x = bself.get().add(bother.clone().clone().getW())). } } float timeelapsed = ((float) (new Date().getW()) .) { Particle bother = (Particle) other.out. other.divide(weightsum).println("Time per step: " + timeelapsed / numsteps + " sec.atEnd(). bself.println("Elapsed time: " + timeelapsed + " sec.println("Steps performed: " + numsteps).x)).getTime() . if (bself != bother) { float radiussum = bself.0f.z). bself.out.").getRadius() + bother. float weightsum = bself. Feb 2002.advance().x.getW()) .out.x.x. if ((bself.println(bself.").divide(weightsum).multiply(bself.multiply(bself. bself. !other.out.x + " " + bself.getRadius(). for (other = (Iterator) self.out.begin(). } // Do collision detection.v.

float otherw) { Vector diff = new Vector(x. // Cache radius to speed up calculations. Vector x. other. Vector v) { this.pow(w / Constants. updateRadius(). return diff. v = new Vector(b. x = new Vector(b. // Position public Vector v. 1. } // Calculate force vector between this particle and the other // one given. public Vector F(Particle other) { return F(other. this.v).sqrt((float) slength))). } Particle(Particle b) { w = b.public class Particle { private float w. float slength = diff. this. public Vector x.MASS_PER_VOLUME. Feb 2002. } public float getRadius() { return radius.x). // Move vector public float getW() { return w.w). // Mass public void updateRadius() { radius = (float) Math. updateRadius().0 / 3.x = new Vector(x).w.w = w. updateRadius().getSquaredLength().negate().subtract(otherx)).multiply(w * otherw * Constants.GRAVITY / (slength * (float) Math. } // Calculate and return force applied between otherx and otherw public Vector F(Vector otherx. } Particle(float w. } } Vector.x. } public void setW(float mass) { w = mass.v = new Vector(v).java ////////////////////////////////////////////////////////////////////////////// // Untweaked particles // by Jacob Marner. 112 .0). } private float radius.

v. float y.x. public float z. return this. y * c. } public Vector add(Vector v) { return new Vector(x + v.x. this. y . } public Vector multiply(float c) { return new Vector(x * c. y = 0. z / c). // ////////////////////////////////////////////////////////////////////////////// public class Vector { public float x.x.z. } public Vector(Vector v) { x = v. } public Vector(float x. float z) { this. z . -z). 113 .v.z).x.y. } public Vector divide(float c) { return new Vector(x / c.z.y = y. } public Vector negate() { return new Vector(-x.y.// // A generic 3D vector class of floats. v.x = x.y.y. -y.y. public Vector() { x = 0.x. y + v. v.z = z. y / c. y = v. this. } public Vector added(Vector v) { x += v.z. } public Vector subtract(Vector v) { return new Vector(x . z = v.z). // Note that objects of this class are mutable. y += v. z * c).v. z + v. public float y. } public { x -= y -= z -= Vector subtracted(Vector v) v. z += v. z = 0.

return this.abs(y)). } public float getInfinityNorm() { return Math. Math. 114 . z /= l. } public Vector normalize() { float l = getLength(). } public void invert() { x = -x. } // Copy vector public Vector assign(Vector v) { x = v. } // Get 2-norm public float getLength() { return (float) Math. Math.max(x.y. z / l).max(Math.abs(x). return new Vector(x / l. } public Vector multiplied(float c) { x *= c. x /= l. y). y /= l. y *= c.max(Math. return this. y = v.x. z *= c. public Vector normalized() { float l = getLength(). y / l.return this. } // Get max of each element (not the same as infinity norm) public float getMax() { return Math. } // Return a normalized version (unit length) of the // vector. y /= c. z /= c. } // Get 2-norm but before the square root is taken public float getSquaredLength() { return x * x + y * y + z * z.abs(z)).sqrt(x * x + y * y + z * z). z = v.z. return this.max(Math. return this. } public Vector divided(float c) { x /= c. z).

z).maxy = maxy.x && y < upperCorner.x * v. Feb 2002.y && z >= lowerCorner. public float maxx. z = -z. } }.x . private Random rand = new Random(new Date(). public float h = -1. -y. resetTraversal().y && z < upperCorner. public World(float maxx.clear(). z * v. } // Return true if this vector is a position between the lower and upper // corner given of a rectangle.maxx = maxx.z * v.*.x). World. } // Add new particle to the world. public float maxz. public void addBody(float mass. } public Vector inverted() { return new Vector(-x. float maxy.getTime()).y . this. Vector upperCorner) { return ( x >= lowerCorner. Vector v) { 115 .z && x < upperCorner.x && y >= lowerCorner. public boolean isInRectangle(Vector lowerCorner. // // The world within which the particles exist.z. public void clear() { bodyList. Each coordinate of lower must be lower // than the same of that of upper. } public Vector cross(Vector v) { return new Vector(y * v.y = -y. x * v. // ////////////////////////////////////////////////////////////////////////////// import java.maxz = maxz. Vector x.y * v.java ////////////////////////////////////////////////////////////////////////////// // Untweaked particles // by Jacob Marner. -z). public class World { private List bodyList = new List(). float maxz) { this.z . this.util. public float maxy. } // Delete all particles in the world.y.

bodyList.pushBack(new Particle(mass, x, v)); } Enumeration it; // Call this before enumerating using getNextBody() public void resetTraversal() { it = bodyList.elements(); } // Enumerate the set of particles/bodies. Call resetTraversal // first. public Particle getNextBody() { if (!it.hasMoreElements()) { return null; } else { return (Particle) it.nextElement(); } } // Generate a random number between 0 and maxValue. public float getRandomfloat(float maxValue) { return rand.nextFloat() * maxValue; } // Generate a random body/particle and add it to the world. public void addRandomBody() { Vector pos = new Vector(getRandomfloat(maxx), getRandomfloat(maxy), getRandomfloat(maxz)); Vector speed = new Vector( getRandomfloat(Constants.MAXRANDOMSPEED * 2) - Constants.MAXRANDOMSPEED, getRandomfloat(Constants.MAXRANDOMSPEED * 2) - Constants.MAXRANDOMSPEED, getRandomfloat(Constants.MAXRANDOMSPEED * 2) - Constants.MAXRANDOMSPEED); bodyList.pushBack( new Particle(getRandomfloat(Constants.MAXRANDOMMASS), pos, speed)); } // Print particle data to screen. Fopr each particle: Weight, pos, speed public void print() { for (Iterator it = bodyList.begin(); !it.atEnd(); it.advance()) { Particle b = (Particle) it.get(); System.out.println( b.getW() + " " + b.x.x + " " + b.x.y + " " + b.x.z + " " + b.v.x + " " + b.v.y + " " + b.v.z); } } // Call this to initialize h if needed. Pass the h option given // on the command line. Giving a negative number will cause h to // be approximated automatically. public void initializeFirstStep(float optionh) { // Only initialize if it has not already been // done. if (h == -1.0) { // If the user did not set h explicitly or set it to // a negative value then approximate it. if (optionh <= 0)

116

{ h = 1; } else { h = optionh; } initializeLeapFrog(); } } // Compute v_1 to start leap frog method. public void initializeLeapFrog() { // Note: There is no need to write v to temporary variable and then // commit changes since F() does not use x. We do not update x // in this first step since we can just use x0. Iterator self; Iterator other; for (self = bodyList.begin(); !self.atEnd(); self.advance()) { Particle bself = (Particle) self.get(); for (other = bodyList.begin(); !other.atEnd(); other.advance()) { Particle bother = (Particle) other.get(); if (bself != bother) bself.v.added(bself.F(bother).multiply(h / bself.getW())); } } } // Add a particle with a given mass, position and direction that is // orbiting around the other given mass and orbit. public void addOrbitalBody( float orbitMass, Vector xn, Vector vn, float centerMass, Vector xo) { Vector v; // Set direction of v by projecting v onto orbit plane. Vector normal = xn.subtract(xo); Vector crossed = normal.cross(vn); v = crossed.cross(normal); // Set length of v v.normalize(); v.multiplied( (float) Math.sqrt(Constants.GRAVITY * centerMass / normal.getLength())); addBody(orbitMass, xn, v); } boolean isPointIn(Vector p) { return ( p.x >= 0 && p.y >= 0 && p.z >= 0 && p.x < maxx && p.y < maxy && p.z < maxz); } }

117

Appendix C: Tweaked Particles Source code
Part 1: C++ source code
The source code consists of the following files: constants.h general.h Main.cpp Vector.h World.cpp World.h A set of constants for use in the program A set of generic functions that is useful for multiple purposes. The main part of the program. A generic 3D vector class of floats. The world within which the particles exist. The header to world.cpp

Constants.h
////////////////////////////////////////////////////////////////////////////// // Tweaked particles // by Jacob Marner. Feb 2002. // // A set of constants (magic numbers) used throughout the // programs // ////////////////////////////////////////////////////////////////////////////// #ifndef CONSTANTS_MODULE #define CONSTANTS_MODULE #include <limits.h> #include <math.h> // The gravitation constant used between particles. const float GRAVITY = (float)(6.67390*pow(10, -11)); // The ratio between mass and volume of particles. // A high value makes the size of particles smaller // given the same mass. const float MASS_PER_VOLUME = 50000; // When particles are generated by random their speed // along each axis will be evenly distributed among // -MAX_RANDOM_SPEED and MAX_RANDOM_SPEED const float MAXRANDOMSPEED = 0; // When particles are generated by random their mass // is set to a random number between 0 and // MAXRANDOMMASS. 0 not included. const float MAXRANDOMMASS = 10000000; // When particles are generated the world is given a // size to achieve constant particle density. High // number gives more space.

118

const float PARTICLEDENSITY = 250000;

#endif

general.h
////////////////////////////////////////////////////////////////////////////// // Tweaked particles // by Jacob Marner. Feb 2002. // // A set of general purpose functions. // ////////////////////////////////////////////////////////////////////////////// #ifndef GENERAL_LIB #define GENERAL_LIB #include #include #include #include <assert.h> <string.h> <stdlib.h> <stdio.h>

#include <windows.h> // Return the absolute value of a template <class Type> Type Abs(const Type a) { return a<0 ? -a : a; } // Return a random int number between low and high, both inclusive. inline int randomInteger(int low, int high) { if (low>high) { int temp = low; low = high; high = temp; } int num = (int)((((double)rand())/(((double)RAND_MAX)+1.0)) * (high-low+1) + low); assert(num>=low); assert(num<=high); return num; } // Search a set of command line arguments for one beginning with label. // If it exist return true. inline bool getBooleanFlag(const char* label, int argc, char* argv[]) { for(int i=0;i<argc;i++) { if (!strncmp(label,argv[i],strlen(label))) { return true; } } return false; } // Search a set of command line arguments for one beginning with label. // If found parse the ramainder of the string as an integer and return // the value. If label was not found return defaultvalue. inline int getIntegerArgument(const char* label, int argc, char* argv[], int defaultvalue) { for(int i=0;i<argc;i++) {

119

if (!strncmp(label,argv[i],strlen(label))) { return atoi(argv[i]+strlen(label)); } } return defaultvalue; } // Search a set of command line arguments for one beginning with label. // If found parse the ramainder of the string as a float and return // the value. If label was not found return defaultvalue. inline float getFloatArgument(const char* label, int argc, char* argv[], float defaultvalue) { for(int i=0;i<argc;i++) { if (!strncmp(label,argv[i],strlen(label))) { char* s = argv[i]+strlen(label); float f; sscanf(s,"%f",&f); return f; } } return defaultvalue; } #endif

main.cpp
////////////////////////////////////////////////////////////////////////////// // Tweaked particles - C++ version // by Jacob Marner. Feb 2002. // // Takes these optonal parameters: // -showprogress : Prints a dot for each step computed. // -steps : The number of steps to compute. Defaults to 100. // -h: The amount of time that passes for each step. Use a negative // value to cause it to be autocomputed. Defaults to autocompution. // -particles: The number of particles in the world to begin with. // Particles that go out of bounds will be deleted. ////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include "constants.h" "Vector.h" "general.h" "world.h" <list> <stdio.h> <float.h> <iostream>

typedef list<Particle> ParticleList; int main(int argc, char* argv[]) { // Read command line arguments bool showprogress = getBooleanFlag("-showprogress",argc,argv); int numsteps = getIntegerArgument("-steps:",argc,argv,100); if (numsteps<0) numsteps=0; float optionh = getFloatArgument("-h:",argc,argv,-1); int numparticles = getIntegerArgument("-particles:",argc,argv,100); if (numparticles<0) numparticles=0; float worldsize = getFloatArgument("-worldsize:",argc,argv, pow(PARTICLEDENSITY * max(1,numparticles),1.0/3.0));

120

World world(worldsize,worldsize,worldsize); for (int i=0;i<numparticles;i++) { world.addRandomParticle(); } world.initializeFirstStep(optionh); cout << "h: " << world.h << endl; // Create particlelist from world ParticleList particles; world.resetTraversal(); float mass; Vector x,v; while (world.getNextParticle(&mass,&x,&v)!=0.0) { particles.push_back(Particle(mass,x,v)); } // Do the steps in the simulation float twoh = world.h * 2; Vector tmp,tmp2; clock_t starttime = clock(); for (int step = 0; step < numsteps; step++) { // Compute one step in the leap-frog method ParticleList::iterator self; ParticleList::iterator other; for (self = particles.begin(); self!=particles.end();++self) { // For particels placed later in the list, than self. // (The once before has already been computed.) for (other = self; other!=particles.end(); ++other) { if (self != other) { self->F(*other,tmp); tmp *= twoh; tmp2 = tmp; tmp /= self->getW(); self->v += tmp; tmp2 /= other->getW(); other->v -= tmp2; } } } for (self = particles.begin(); self!=particles.end();) { // Compute new x value tmp = self->v; tmp *= twoh; self->x += tmp; // Detect if particle is out of the world bounds. If so: delete. if (!world.isPointIn(self->x)) { self = particles.erase(self); } else ++self; } // Do collision detection. for (self = particles.begin(); self!=particles.end(); ++self) { for (other = self; other!=particles.end();) { if (self != other) {

121

begin(). } return 0.it->v).it->x. it!=particles.other->x). } Vector.float radiussum = self->getRadius() + other->getRadius()." << endl.h> "constants." << flush.h ////////////////////////////////////////////////////////////////////////////// // Tweaked particles // by Jacob Marner. } else ++other. if ((self->x .starttime)) / CLOCKS_PER_SEC.erase(other).h" <math. // // A generic 3D vector class of floats // ////////////////////////////////////////////////////////////////////////////// #ifndef VECTOR_CLASS #define VECTOR_CLASS #include #include #include #include "general.addParticle(it->getW().++it) { world.getSquaredLength() <= radiussum * radiussum) { // Collision detected: Combine the two particles in one.end(). self->x += other->x * otherw. } float timeelapsed = ((float)(clock() . self->v /= weightsum.clear(). world. for (ParticleList::iterator it = particles. self->x *= selfw. float weightsum = selfw + otherw.h" <stddef." << endl. } else ++other. float selfw = self->getW(). if (numsteps!=0) cout << "Time per step: " << timeelapsed / numsteps << " sec. cout << "Steps performed: " << numsteps << endl. self->x /= weightsum. } } if (showprogress) cout << ". if (showprogress) cout << endl. cout << "Elapsed time: " << timeelapsed << " sec. 122 . // Delete the particle pointed to by other. self->v += other->v * otherw. Feb 2002. self->v *= selfw. float otherw = other->getW(). self->setW(weightsum).h> class Vector { public: float x. other = particles.

z(v. return *this. } inline Vector operator-(void) const { return Vector(-x.x).x. } inline Vector& operator-=(const Vector& v) { x -= v. z(0) {} Vector(const float x. } inline Vector& operator*=(const float c) { x *= c.z.y+v.y. y /= c.y*c. y = v.x. z(z) {} Vector(const Vector& v) : x(v.y.x.z).-z). z += v. 123 .float y. z -= v.y.z).y). return *this.y/c.x. z /= c. float z. y(0).x.z-v. } inline Vector operator-(const Vector& v) const { return Vector(x-v.y.z. z = v. } // Copy vector inline Vector& operator=(const Vector& v) { x = v.z/c).y. y += v. z *= c. return *this. } inline Vector operator/(const float c) const { return Vector(x/c. const float z) : x(x).z*c). return *this.z) {} inline Vector& operator+=(const Vector& v) { x += v.-y.z. return *this. } inline Vector operator*(const float c) const { return Vector(x*c. y -= v. y(y). } inline Vector operator+(const Vector& v) const { return Vector(x+v.z+v. Vector(void) : x(0). const float y. y *= c.y-v. y(v. } inline Vector& operator/=(const float c) { x /= c.

z /= l.y && z < upperCorner. const Vector& upperCorner) const { return (x >= lowerCorner.x && y >= lowerCorner.-z). y /= l.z*v.x-x*v. z / l).Abs(y)).x*v.z && x < upperCorner. } // Get max of each element (not the same as infinity norm) inline float getMax(void) const { return max(max(x. } inline float getInfinityNorm(void) const { return max(max(Abs(x).y && z >= lowerCorner.y. inline Vector normalized(void) const { float l = getLength(). } }. inline bool isInRectangle(const Vector& lowerCorner. x /= l. Each coordinate of lower must be lower // than the same of that of upper.} // Get 2-norm inline float getLength(void) const { return (float)sqrt(x*x+y*y+z*z). } // Return true if this vector is a position between the lower and upper // corner given of a rectangle.x && y < upperCorner. y = -y.z-z*v. return *this. #endif 124 .x). } inline Vector inverted(void) const { return Vector(-x. } // Return a normalized version (unit length) of the // vector.-y. } inline Vector& normalize(void) { float l = getLength().z.Abs(z)). z = -z.z).z). } inline Vector cross(const Vector& v) const { return Vector(y*v.y-y*v. } // Get 2-norm but before the square root is taken inline float getSquaredLength(void) const { return x*x+y*y+z*z. } inline void invert(void) { x = -x. y / l. return Vector(x / l.y).

*x = b. assert(r>=0. assert(r<=1.0).0). *x = Vector(0.clear(). float World::getRandomfloat(float maxValue) { // Find float in interval 0 <= r <= maxValue float r = ((float)rand())/(((float)((float)RAND_MAX))+1.cpp ////////////////////////////////////////////////////////////////////////////// // Tweaked particles // by Jacob Marner.x.h> <assert.begin().world.h> // Delete all particles in the world. } // Call this before enumerating using getNextParticle() void World::resetTraversal(void) { it = particleList.getW(). const Vector& v) { particleList. Call resetTraversal // first. const Vector& x. } else { Particle b = *it. *mass = b. return 0.0). return *mass.0).x. srand(time(NULL)). } // Enumerate the set of particles/bodies. void World::addParticle(float mass. } 125 .h" <stdio.0). // // The world within which the particles exist. *v = Vector(0. Feb 2002. } // Add new particle to the world. *v = b. ++it.0.v)).push_back(Particle(mass.end()) { *mass = 0. void World::clear(void) { particleList. Vector* x. } } // Generate a random number between 0 and maxValue.v.0.h> <float.h> <iostream> <limits. return r*maxValue. // ////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include "world. float World::getNextParticle(float* mass. Vector* v) { if (it == particleList.

Vector speed(getRandomfloat(MAXRANDOMSPEED*2)-MAXRANDOMSPEED. } initializeLeapFrog(). We do not update x // in this first step since we can just use x0. ParticleList::iterator self. } else { h = optionh.v.pos. position and direction that is // orbiting around the other given mass and orbit.getW() << " " << b. } // Print particle data to screen. Pass the h option given // on the command line. Fopr each particle: Weight.y << " " << b.end().end(). pos. for (self = particleList.v.x << " " << b. void World::addRandomParticle(void) { Vector pos(getRandomfloat(maxx).x.speed)).begin(). speed void World::print(void) { for (ParticleList::iterator it = particleList. ++it) { Particle b = *it.z << endl. it!=particleList. Giving a negative number will cause h to // be approximated automatically. ++self) { for (other = particleList. cout << b.begin(). if (h==-1.getRandomfloat(maxz)). if (optionh<=0) { h = 1.v. void World::initializeFirstStep(float optionh) { // Only initialize if it has not already been // done. void World::initializeLeapFrog() { // Note: There is no need to write v to temporary variable and then // commit changes since F() does not use x.end().// Generate a random particle/particle and add it to the world. } } } // Add a particle with a given mass.0) { // If the user did not set h explicitly or set it to // a negative value then approximate it.y << " " << b.x. } } // Call this to initialize h if needed. getRandomfloat(MAXRANDOMSPEED*2)-MAXRANDOMSPEED.x << " " << b. particleList. ++other) { if (self!=other) self->v += self->F(*other)*(h/self->getW()).z << " " << b.push_back(Particle(getRandomfloat(MAXRANDOMMASS). ParticleList::iterator other. other!=particleList.begin().getRandomfloat(maxy). self!=particleList. 126 .x. } } // Compute v_1 to start leap frog method. getRandomfloat(MAXRANDOMSPEED*2)-MAXRANDOMSPEED).

cross(normal). public: Vector x.h> <string> "time. const Vector& xn. Feb 2002. v = crossed.h" "Vector. } world.1.h ////////////////////////////////////////////////////////////////////////////// // Tweaked particles // by Jacob Marner. float centerMass.0/3. // Position Vector v. // ////////////////////////////////////////////////////////////////////////////// #ifndef WORLD_MODULE #define WORLD_MODULE #include #include #include #include #include #include #include "constants. const Vector& vn. updateRadius(). addParticle(orbitMass. } inline float getRadius(void) { 127 . // Set direction of v by projecting v onto orbit plane. Vector normal = xn-xo. const Vector& xo) { Vector v.h" // Retrieve STL classes into default namespace. class Particle { float w.cross(vn).getLength()).normalize().0).h" <list> <ostream> <stdio. using namespace std. // Move vector inline float getW(void) { return w. } float radius. } inline void setW(float mass) { w = mass. Vector crossed = normal. v *= sqrt(GRAVITY * centerMass / normal. // // The world within which the particles exist. // Mass inline void updateRadius() { radius = (float)pow(w/MASS_PER_VOLUME.void World::addOrbitalParticle(float orbitMass. // Set length of v v. xn. v).

other.getSquaredLength().return radius. // Iterator for traversal ParticleList::iterator it. public: // Stepsize float h. float maxz) : h(-1). void addRandomParticle(void). float maxy. x(x). typedef list<Particle> ParticleList. } inline void F(const Particle& other. maxz(maxz) { srand(time(NULL)). // Create a new world with the given bounderies.x. // Add many random bodies to the world. // Return random float in range 0 < x <= maxValue. // Legal range are 0 <= max<a> < maxValue float maxx. maxx(maxx). maxy(maxy). float maxz. it = particleList.other. return -(diff*(w*otherw*GRAVITY/(slength*sqrt((float)slength)))). void initializeLeapFrog(void). } Particle(float w. } }. const Vector& v) : w(w). class World { private: ParticleList particleList. World(float maxx.begin(). void addRandomBodies(int count). } // Calculate and return force applied between otherx and otherw inline Vector F(const Vector& otherx. v(v) { updateRadius().x. result *= -w*other. Vector& result) { result = x . float slength = diff. float slength = result.w). const Vector& x.otherx). } inline Vector F(const Particle& other) { return F(other. // Add a Particle to this world with the given mass and speed.w*GRAVITY/(slength*sqrt((float)slength)). float otherw) { Vector diff(x . float getRandomfloat(float maxValue). 128 .getSquaredLength(). // Performs the first step of the leap frog algorithm. } // Add a random Particle to the world. float maxy.

// Delete all bodies in this world. inline bool isPointIn(const Vector& p) { return (p.void addParticle(float mass. float getNextParticle(float* mass. // Get the next Particle. // Return the size of the world.y < maxy && p. const Vector& v1. // Returns true if the given vector falls within // the world bounderies. } // Assuming that the initialization has not yet occured // for this world (if it has then nothing happens) then // this will give h a value and then perform the first step // of the leapfrog algorithm.e. // Add a new Particle to the world such that it will orbit around a Particle // given by the second set of coordinates.z < maxz). } }.y >= 0 && p.maxy. // Start a traversal of the bodies void resetTraversal(void). float centerMass. const Vector& x2).x < maxx && p. const Vector& x1. void print(void). void clear(void).size(). // otherwise calculate approximation. #endif 129 . // Is printed in the form: "mass position speed" for // each line. Returns 0 when done. Vector* v).maxz). const Vector& x. If optionh is set to a positive // value (i.z >= 0 && p. h was set from the command line) use that h. Vector* x. // Return the number of bodies in this world inline int getSize() { return particleList. Vector getWorldExtent(void) { return Vector(maxx.x >= 0 && p. const Vector& v). void initializeFirstStep(float optionh). The speed given will // be project down to orbital plane and the speed // may change. } // Print the bodies to the standard output stream. void addOrbitalParticle(float orbitMass.

java Vector. Arguments.java ////////////////////////////////////////////////////////////////////////////// // Tweaked particles // by Jacob Marner.java This file is used when parsing command line arguments A set of constants defining the behavior of the particle simulator. The world within which the particles exist.length.length.java List.java Main. Feb 2002. // // This file is used when parsing command line arguments // ////////////////////////////////////////////////////////////////////////////// final public class Arguments { String[] args. i++) { if (args[i].Part 2: Java source code The source code consists of the following files: Arguments.equals(label)) { return true.args = args.java Particle. int defaultvalue) { for (int i = 0.java Iterator. An iterator for a singly linked list classA singly linked list class The Main class. i < args. Objects of this class represents a single particle A generic 3D vector class of floats. } } return false. } public int getIntegerArgument(String label. This is where the timed calculation occurs. } public boolean getBooleanFlag(String label) { for (int i = 0. i++) { 130 .java Constants. i < args.java World. public Arguments(String[] args) { this.

float defaultvalue) { for (int i = 0. public static final float MAXRANDOMMASS = 10000000. i++) { if (args[i]. } catch (NumberFormatException e) { return defaultvalue. public static final float MASS_PER_VOLUME = 50000.length()). } } } return defaultvalue. } catch (NumberFormatException e) { return defaultvalue. // The ratio between mass and volume of particles. Feb 2002. // A high value makes the size of particles smaller // given the same mass. 0 not included.if (args[i].substring(label. try { return Integer.length.startsWith(label)) { String s = args[i]. i < args.parseFloat(s). // ////////////////////////////////////////////////////////////////////////////// final public class Constants { public static final float GRAVITY = (float) (6.startsWith(label)) { String s = args[i]. -11)). try { return Float.length()). } public float getFloatArgument(String label. // When particles are generated by random their speed // along each axis will be evenly distributed among // -MAX_RANDOM_SPEED and MAX_RANDOM_SPEED public static final float MAXRANDOMSPEED = 0. } } } return defaultvalue.parseInt(s). // When particles are generated the world is given a // size to achieve constant particle density. High // number gives more space.67390 * Math. // When particles are generated by random their mass // is set to a random number between 0 and // MAXRANDOMMASS.java ////////////////////////////////////////////////////////////////////////////// // Tweaked particles // by Jacob Marner.substring(label. 131 .pow(10. // // A set constants defining the behavior of // particle simulator. } } Constants.

} public boolean atEnd() { return myNode == null.java ////////////////////////////////////////////////////////////////////////////// // Tweaked particles // by Jacob Marner. } public Particle nextElement() { 132 .Node node) { mySList = list.next. } Iterator. } public boolean hasMoreElements() { return myNode != null.myNode. return this. } public void advance() { myNode = myNode. } public boolean atBegin() { return myNode == mySList. public Iterator() { } Iterator(List list.Node myNode.mySList.IOException. // // An iterator for a singly linked list class. myNode = iterator. myNode = node. transient List.io. myNode = iterator. } public Iterator assign(Iterator iterator) { mySList = iterator. } public Iterator(Iterator iterator) { mySList = iterator. // ////////////////////////////////////////////////////////////////////////////// import java.myNode. Feb 2002. } public Object clone() { return new Iterator(this).public static final float PARTICLEDENSITY = 250000. List.head.mySList. public final class Iterator { List mySList.

public List() { } public Iterator elements() { return new Iterator(this. transient int length.io. } public Iterator begin() { return new Iterator(this. // ////////////////////////////////////////////////////////////////////////////// import java. } public int size() { return length. public final class List { transient Node head.IOException. import java. null).next. } } List.object. transient Node tail. 133 .util. head).Enumeration. Feb 2002. myNode = myNode.try { Particle object = myNode. return object. } public Iterator end() { return new Iterator(this.object.util. } public boolean isEmpty() { return length == 0. } public void clear() { head = null. } } public Particle get() { return myNode. // // A singly linked list class. head).java ////////////////////////////////////////////////////////////////////////////// // Tweaked particles // by Jacob Marner.NoSuchElementException("SListIterator"). } catch (NullPointerException ex) { throw new java.

lang. node = node. node != tail. if (++length == 1) head = node.next = head. previous. Particle r = head. node.next.object.tail = null.object = object.object. node. Particle r. return r. if (--length == 0) tail = null. length = 0. if (previous == null) { r = head. } tail = previous.object = object. Node previous = null.next. for (Node node = head. return r.object. } public Particle popBack() { if (length == 0) throw new java. else tail. node.next = node. head = head. if (++length == 1) tail = node.Error("SList is empty"). --length. } public void add(Particle object) { Node node = new Node(). } public void pushFront(Particle object) { Node node = new Node(). head = node. tail = node. } else { r = previous.next) previous = node. } public void pushBack(Particle object) { 134 .next = null. } public Particle popFront() { if (length == 0) throw new Error("Slist is empty"). head = null.

// -particles: The number of particles in the world to begin with. remove(((Iterator) pos). // Particles that go out of bounds will be deleted. This is where the timed calculation occurs.util. Feb 2002. boolean showprogress = arguments.add(object).next. final public class Main { public static void main(String[] args) { Arguments arguments = new Arguments(args). // -h: The amount of time that passes for each step. for (Node node = head.next = target. Defaults to autocompution. node != target.*. if (target == tail) tail = previous. if (numsteps < 0) 135 . } } Main.java ////////////////////////////////////////////////////////////////////////////// // Tweaked particles // by Jacob Marner. } public void remove(Iterator pos) { if (!(pos instanceof Iterator)) throw new IllegalArgumentException("Enumeration not a SListIterator"). Defaults to 100. if (previous == null) head = target. Use a negative // value to cause it to be autocomputed.object. } private Particle remove(Node target) { Node previous = null. // -steps : The number of steps to compute. node = node.mySList != this) throw new IllegalArgumentException("Enumeration not for this SList").getBooleanFlag("-showprogress"). } final static class Node { public Node next = null. if (((Iterator) pos). return target. // // The Main class.next) previous = node. ////////////////////////////////////////////////////////////////////////////// import java.get(). public Particle object = null.getIntegerArgument("-steps:". ((Iterator) pos).myNode). 100).next. int numsteps = arguments. else previous. --length. // // Takes these parameters: // -showprogress : Prints a dot for each step computed.

begin(). Iterator self. for (int step = 0. // Create particlelist from world List particles = new List().pushBack(new Particle(b)). other.advance()) { bother = other.begin().begin().0)).v.get().h).assign(self). (float) Math. float optionh = arguments.atEnd(). float worldsize = arguments. Particle bself.added(tmp).divided(bother. Particle bother. numparticles). float selfw = bself. } world. if (numparticles < 0) numparticles = 0. } } } 136 . if (bself != bother) { bself.subtracted(tmp2). tmp. int numparticles = arguments.get(). bself. than self.0 / 3. Iterator tmpit = (Iterator) particles.F(bother.h + world.PARTICLEDENSITY * Math. while ((b = world. i++) { world. 100).addRandomBody(). } // Do the steps in the simulation float twoh = /* world.divided(bself. -1).println("h: " + world.getW()).advance()) { bself = self.max(1.getIntegerArgument("-particles:". worldsize. // (The once before has already been computed.resetTraversal().getW()).out.) for (other. Iterator other = (Iterator) particles. World world = new World(worldsize. tmp. tmp2. i < numparticles.initializeFirstStep(optionh).getFloatArgument( "-worldsize:". bother. !self. 1. 0. step++) { // Compute one step in the leap-frog method for (self = particles. Particle b. step < numsteps. Date starttime = new Date().getFloatArgument("-h:". // For particels placed later in the list.pow( Constants.0f */ world. 0.getNextBody()) != null) { particles.multiplied(twoh). Vector tmp = new Vector(0.h * 2. System.getW(). self. tmp2. 0).v. Vector tmp2 = new Vector(0. worldsize). !other. 0).numsteps = 0. world. for (int i = 0.h.assign(tmp).atEnd(). tmp).

get(). } // Do collision detection.x.atEnd().getSquaredLength() <= radiussum * radiussum) { // Collision detected: Combine the two particles in one. !self. } else other.advance(). System.multiplied(bself.assign(self).advance()) { bself = self.setW(weightsum).getTime() .0f.advance().advance(). float weightsum = bself. if (!world. bself.v.isPointIn(bself.getRadius().atEnd(). particles.v.assign(bself.get().println().divided(weightsum).out.x)) { tmpit.added(bother.assign(bself. bself.x. bself. } } if (showprogress) { System. if (showprogress) System. tmp.x).x).out.getW().for (self = particles.").begin(). tmp. if (bself != bother) { float radiussum = bself.getRadius() + bother. if (numsteps != 0) 137 .advance().v.println("Steps performed: " + numsteps).println("Elapsed time: " + timeelapsed + " sec.advance().multiplied(twoh).remove(tmpit).multiplied(bself.) { bself = self.x. tmp. !self. particles. bself.flush().getW())).) { bother = other.x.print(".get().starttime. bself.multiply(bother.assign(self).divided(weightsum).multiply(bother. If so: delete. tmpit.out.added(tmp). for (other. !other.getW() + bother.added(bother.getW()). bself.assign(other).getW()).out.getTime())) / 1000. } } float timeelapsed = ((float) (new Date().getW())).x. for (self = (Iterator) particles."). // Compute new x value tmp. self. other. bself. if (tmp. self.atEnd(). bself. System.remove(tmpit).subtracted(bother. } else self.v). System. // Delete the particle pointed to by other.v. } else other.begin().out. // Detect if particle is out of the world bounds.

updateRadius().System.negated(). diff. this.subtract(otherx)). float otherw) { Vector diff = new Vector(x. x = new Vector(b. // Position public Vector v. final Vector x.out. updateRadius(). 1. } final public void setW(float mass) { w = mass.0). float slength = diff. this.println("Time per step: " + timeelapsed / numsteps + " sec. // Move vector final public float getW() { return w. v = new Vector(b.0 / 3.v = new Vector(v).w.w = w. } // Calculate and return force applied between otherx and otherw final public Vector F(final Vector otherx. diff. } } Particles. return diff.GRAVITY / (slength * (float) Math. // Mass public void updateRadius() { radius = (float) Math.MASS_PER_VOLUME. public Vector x. // // Objects of this class represents a single particle // ////////////////////////////////////////////////////////////////////////////// final public class Particle { private float w. } Particle(float w.pow(w / Constants.").java ////////////////////////////////////////////////////////////////////////////// // Tweaked particles // by Jacob Marner. } final public float getRadius() { return radius.v). final Vector v) { this. Feb 2002.x = new Vector(x). } 138 . } private float radius.x). } Particle(final Particle b) { w = b.sqrt((float) slength))).multiplied(w * otherw * Constants. updateRadius().getSquaredLength().

public float z.v.z). } final public Vector F(final Particle other) { return F(other. // ////////////////////////////////////////////////////////////////////////////// final public class Vector { public float x. public float y.x = x. z = v.x.v. result. 0.y. public Vector() { x = 0. Vector result) { Vector otherx = other.x.w. float y.z = z. other. float otherw = other. z = 0. } public Vector subtract(Vector v) { return new Vector(x . y = 0.y = y. 0).x.y.GRAVITY / (slength * (float) Math. // Note that objects of this class are mutable. } 139 . Feb 2002.negated().getSquaredLength().w).v.static Vector tmp = new Vector(0. y + v. float z) { this.assign(x).x. // // A generic 3D vector class of floats. this. // Tweaked F final public void F(final Particle other. float slength = result. } public Vector(float x.x. } public Vector add(Vector v) { return new Vector(x + v. result.subtracted(otherx). result. } } Vector. y .y.z. y = v.multiplied(w * otherw * Constants. z . this. result.sqrt((float) slength))). } public Vector(Vector v) { x = v.z). z + v.java ////////////////////////////////////////////////////////////////////////////// // Tweaked particles // by Jacob Marner.

y.z. } // Get 2-norm public float getLength() { return (float) Math.max(x. c. v. y = -y. c. v. // Copy vector public void assign(Vector v) { x = v. y = v. v. z = v.sqrt(x * x + y * y + z * z). } public { x += y += z += } public { x -= y -= z -= } public { x *= y *= z *= } public { x /= y /= z /= } void added(Vector v) v. z = -z. void subtracted(Vector v) v. } public Vector negate() { return new Vector(-x.y. y * c.x. z / c). z). y). } // Get max of each element (not the same as infinity norm) public float getMax() { return Math.x.x. z * c).z. -y.y. } 140 . void multiplied(float c) c. void divided(float c) c.z. v. c. y / c. -z). c.max(Math. } public Vector multiply(float c) { return new Vector(x * c. } public void negated() { x = -x.public Vector divide(float c) { return new Vector(x / c.

z = -z. -z). } public void invert() { x = -x. World. Vector upperCorner) { return ( x >= lowerCorner.z && x < upperCorner.z .x && y >= lowerCorner. } // Get 2-norm but before the square root is taken public float getSquaredLength() { return x * x + y * y + z * z.z). } // Return true if this vector is a position between the lower and upper // corner given of a rectangle.public float getInfinityNorm() { return Math. y / l. z * v.x .x). z /= l.java ////////////////////////////////////////////////////////////////////////////// // Tweaked particles // by Jacob Marner. x * v. public boolean isInRectangle(Vector lowerCorner. } public Vector cross(Vector v) { return new Vector(y * v. // // The world within which the particles exist. Feb 2002.abs(y)).*.util. } }. public Vector normalized() { float l = getLength().y * v.y && z >= lowerCorner.y . 141 .abs(z)). z / l).max(Math. } public Vector inverted() { return new Vector(-x. } public void normalize() { float l = getLength().z.y. -y.max(Math. Math. y /= l. Math. // ///////////////////////////////////////////////////////////////////////////// import java. x /= l.y && z < upperCorner. Each coordinate of lower must be lower // than the same of that of upper. return new Vector(x / l.z * v. } // Return a normalized version (unit length) of the // vector.x * v.abs(x).x && y < upperCorner. y = -y.

public float getRandomfloat(float maxValue) { return rand. } // Add new particle to the world. Vector x. } else { return (Particle) it. x.final public class World { private List bodyList = new List().nextFloat() * maxValue. float maxz) { this. getRandomfloat(maxy).MAXRANDOMSPEED * 2) .MAXRANDOMSPEED * 2) .maxx = maxx. public void addBody(float mass. getRandomfloat(maxz)). this.nextElement().getTime()). this.Constants.hasMoreElements()) { return null. getRandomfloat(Constants.Constants. Vector v) { bodyList. // Call this before enumerating using getNextBody() public void resetTraversal() { it = bodyList. } } // Generate a random number between 0 and maxValue.MAXRANDOMSPEED.Constants. resetTraversal().maxy = maxy.maxz = maxz. 142 .MAXRANDOMSPEED). Call resetTraversal // first. public float maxx. } // Delete all particles in the world. Vector speed = new Vector( getRandomfloat(Constants.elements(). public Particle getNextBody() { if (!it. float maxy. public void clear() { bodyList. public float h = -1. public void addRandomBody() { Vector pos = new Vector(getRandomfloat(maxx).MAXRANDOMSPEED * 2) . v)). getRandomfloat(Constants. } Iterator it. public World(float maxx.pushBack(new Particle(mass. } // Enumerate the set of particles/bodies.MAXRANDOMSPEED. } // Generate a random body/particle and add it to the world. public float maxy. public float maxz.clear(). private Random rand = new Random(new Date().

advance()) { Particle b = (Particle) it. !other.bodyList.v.y + " " + b. pos.getW())). it. if (h == -1.get(). } } } // Add a particle with a given mass.pushBack( new Particle(getRandomfloat(Constants.0) { // If the user did not set h explicitly or set it to // a negative value then approximate it. for (self = bodyList. speed public void print() { for (Iterator it = bodyList. public void initializeFirstStep(float optionh) { // Only initialize if it has not already been // done. self. !self.multiply(h / bself. } } // Compute v_1 to start leap frog method.atEnd(). } else { h = optionh. } // Print particle data to screen.y + " " + b. } } // Call this to initialize h if needed.get().advance()) { Particle bother = (Particle) other.x.begin(). Pass the h option given // on the command line. For each particle: Weight. Giving a negative number will cause h to // be approximated automatically. for (other = bodyList. if (bself != bother) bself. if (optionh <= 0) { h = 1.begin().advance()) { Particle bself = (Particle) self. We do not update x // in this first step since we can just use x0.atEnd().v. position and direction that is 143 . Iterator self.x.z + " " + b.getW() + " " + b. public void initializeLeapFrog() { // Note: There is no need to write v to temporary variable and then // commit changes since F() does not use x.x.x + " " + b. System.get(). other. !it.F(bother).begin(). speed)).added(bself.v.z).MAXRANDOMMASS).atEnd(). Iterator other.println( b.out.x + " " + b.v. pos. } initializeLeapFrog().

x < maxx && p. v = crossed. // Set length of v v.z < maxz).subtract(xo). // Set direction of v by projecting v onto orbit plane.cross(vn).cross(normal).z >= 0 && p.y < maxy && p. Vector normal = xn. } } 144 . Vector crossed = normal.y >= 0 && p. public void addOrbitalBody( float orbitMass. Vector xo) { Vector v. Vector xn. } boolean isPointIn(Vector p) { return (p.x >= 0 && p.sqrt(Constants. float centerMass. Vector vn.// orbiting around the other given mass and orbit.getLength())). xn. v).multiplied( (float) Math.normalize(). v. addBody(orbitMass.GRAVITY * centerMass / normal.

}. y(y) {} Location& operator=(const Location& l) { x = l. y = l. // // Takes two integer parameters.cpp ////////////////////////////////////////////////////////////////////////////// // Untweaked Life .x. 145 . Feb 2002.0) { }.y. class Society. struct Location { Location(int x. int y) : x(x). ////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include <queue> <algorithm> <iostream. // 2. main.C++ // by Jacob Marner. The number of time steps to run.h> <vector> <assert. protected: friend Society.h> using namespace std. int y. The size of the world as given as the side length of a square world. virtual void action(const Society& oldSociety.cpp The application. virtual void draw()=0. return *this. class Entity { public: Entity(void) :location(0.h> <time. // 1.Appendix D: Untweaked Life Source code Part 1: C++ source code The source code consists of just one source file: main. } int x. virtual Entity& operator=(Entity&). Society& newSociety) { }.

} } void Society::destroyCells() { for (int j=0.resize(size.y). Location size. // time since start. ///////////////////////////////////////////////////// // Implementation ///////////////////////////////////////////////////// Society::Society(const Location& size.Location location.front(). typedef vector<vector<Entity* > > areatype. void registerDeath(Entity* e).y. t(starttime) { area.j++) for(int k=0. void draw(void) const. areatype area.i++) area[i].resize(size. }.j++) for(int k=0.x. Entity*).j<size. They are not // owned by the society. for (int j=0. int starttime) : size(size).k++) area[j][k]=NULL.k<size. void timetick(void). int t.y. private: Society& operator=(const Society&).x. void destroyCells(void). void setEntity(const Location& pos.x). Entity* getEntity(const Location& pos) const.k<size. deathrow.j<size. bool legalpos(const Location& pos) const.k++) if (area[j][k]!=NULL) 146 .i<size. class Society { public: Society(const Location& size. // Free dead cells while (!deathrow.empty()) { delete deathrow. Location getsize() const. }. } Society::~Society() { // Do not free regular cells here. int starttime). queue<Entity* > deathrow.x. ~Society(). for (int i=0.pop().

x++) cout << "-". Society& Society::operator=(const Society& ns) { assert (size.x. Note that the deathrow is not // copied and that the cells are not copied either because // they are not owned by the society. cout << "+" << endl. assert(pos.y]=e.x][pos.y < size. for(y=0.x).x. cout << "+" << endl. for(y=0.*newsociety).x.x < size. Entity* e.y.x.y<size. } void Society::draw(void) const { int x.y.x++) { if((e=area[x][y])!=NULL) e->action(*this. } void Society::timetick(void) { int x.x<size.y. Entity* e) { assert(pos.y == ns.y. } // Copy the society. cout << "Time cycle: " << t << endl.t+1). } void Society::setEntity(const Location& pos.x < size. } cout << "|" << endl.y < size.y).x < size. for(x=0. assert (size.x>=0). else cout << " ". e->location = pos.size. } cout << "+". t = ns.size. Society* newsociety = new Society(size. area = ns.x++) { if((e=area[x][y])!=NULL) e->draw(). for(x=0. assert(pos. cout << "+".y++) { cout << "|".x == ns.delete area[j][k]. delete newsociety.y++) for(x=0. Entity* e. for(x=0. area[pos.y>=0). assert(pos.x). } *this = *newsociety.t.y).x < size. return *this.x++) cout << "-". 147 .area.

const Location& pos) { int count=0.0.0.-1.legalpos(l) && s. } return count. if ((n>1) && (n<4) && !newSociety.0. for (int i=0.} Entity* Society::getEntity(const Location& pos) const { assert(pos.y).y].location. if(s.-1. } Entity& Entity::operator=(Entity& e) { location = e.x+nx[i].x && pos. } Location Society::getsize() const { return size.y<size.y>=0).-1.location).1}.1.this).y>=0 && pos. Society& newSociety) { // If the cell has 2 or 3 neighbours it survives int n = countNeighbours(oldSociety.x<size. // If a neighbour has 3 neighbours then a new is 148 .i++) { Location l(pos.x>=0 && pos.getEntity(l)!=NULL) count++. assert(pos.1. } bool Society::legalpos(const Location& pos) const { return pos. } virtual void action(const Society& oldSociety.x][pos.y. assert(pos.1.i<8.x).0. } virtual void draw(void) { cout << "*".pos. else newSociety. return area[pos.push(e).y<size.1. return *this. assert(pos.setEntity(location.1.x<size.y+ny[i]).getEntity(location)) newSociety.registerDeath(this). const int ny[] = {-1. } //////////////////////////////////////////////////// // Cell definition ///////////////////////////////////////////////////// const int nx[] = {-1.x>=0).-1}. class Cell : public Entity { public: Cell() : Entity() { } int countNeighbours(const Society& s. } void Society::registerDeath(Entity* e) { deathrow.

location.destroyCells().y<worldsize.getEntity(l)) { newSociety. //mysociety.// born there. int endtime = clock(). return 1.x<worldsize. inline int randomInteger(int high) { int num = (int)((((double)rand())/(((double)RAND_MAX)+1. //mysociety.setEntity(l.draw().new Cell()).setEntity(Location(x. for (int i = 0. return 0. int steps = atoi(argv[2]).new Cell()). if (oldSociety.y++) if (randomInteger(10)<4) mysociety. srand(time(NULL)). return num.x++) for (int y=0. assert(num<=high).0).j<steps.y).0)) * (high+1)). } mysociety. } 149 .y+ny[i]). int starttime = clock(). for (int j=0. char* argv[]) { if (argc<3) { cout << "Usage: life <worldsize> <steps>" << endl.draw().x+nx[i].legalpos(l) && countNeighbours(oldSociety. } } } }. i++) { Location l(location.timetick(). assert(num>=0).j++) { mysociety. } int worldsize = atoi(argv[1]). Society mysociety(Location(worldsize.worldsize). cout << "Time elapsed: " << ((double)(endtime-starttime))/CLOCKS_PER_SEC << " sec.l)==3 && !newSociety. } //////////////////////////////////////////////////////// // Main //////////////////////////////////////////////////////// int main(int argc. for(int x=0. i<8." << endl.

y = y.Part 2: Java source code The source code consists of just one source file: Main. } protected Location lo. Main. } Location getLocation() { return lo. // 2.java ////////////////////////////////////////////////////////////////////////////// // Untweaked Life // by Jacob Marner.y + y). lo.util. this. } Location add(Location lo) { return new Location(lo. import java. } abstract class entity { entity(society s. } 150 . Feb 2002. class Location { Location(int x.setentity(getLocation(). Location lo) { this. this).java The application.PrintStream. The size of the world as given as the side length of a square world. } void action(society S) { S.s = s. // // Takes two integer parameters.x + x. } char draw() { return 'x'. // 1. ////////////////////////////////////////////////////////////////////////////// import java. The number of time steps to run.io.*. int y) { this. } int x.lo = lo.x = x. int y. this. protected society s.

y.draw()).x.println("Turn: " + t).size = size.x. ps.action(ns). x < size.print(e[x][y].y < size.print(' ').print('+'). ++x) ps. for (int y = 0.print('+').println(). x < size.y >= 0 && lo. } void timetick() { society ns = new society(size).print('-').x][lo. for (int y = 0.x][size.size.x && lo.x.println('|').out. t = ns. ps. for (int x = 0. for (int x = 0. ++x) if (e[x][y] != null) e[x][y].y] = ent. x < size. entity ent) { e[lo.x < size. } 151 .y) return true. private Location size.print('+').x >= 0 && lo.print('+'). ps.print('-'). ++x) ps. y < size. size = ns.x][lo.e. ++x) if (e[x][y] != null) ps. for (int x = 0. ps. } boolean legalpos(Location lo) { if (lo. ps. ++y) { ps.y].y]. } void draw() { PrintStream ps = System. return false.x.t. } entity getentity(Location lo) { return e[lo.class society { society(Location size) { this.y. x < size. } private entity[][] e. y < size. } ps.print('|'). e = new entity[size. ++y) for (int x = 0. ps. ps. e = ns. else ps. } void setentity(Location lo. private int t.println().

setentity(lo. private int count_neighbours(Location lo) { int count = 0. ++i) { Location l = neighbour_list[i].parseInt(args[1]). new Location(-1. static final Location[] neighbour_list = { new Location(-1. if (s. if (s. new Location(1. } char draw() { return '*'. for (int i = 0. } void action(society ns) { int n = count_neighbours(lo). new cell(s. } static final int neighbours = 8.err.err.length!=2) { System.class cell extends entity { cell(society s. The initial cell density is 40%. new Location(1. i < neighbours. Location lo) { super(s.getentity(l) != null) ++count. if (n > 1 && n < 4 && ns.println(" with a sqaure world with room for <worldsize>*<worldsize>"). l)). 152 . } return count.println(" Will calculate the game of life for <steps> time steps").err. } } } public class Main { public static void main(String[] args) { if (args. By Jacob Marner."). -1). 0).parseInt(args[0]). i < neighbours.println(" cells. System. 1).err. new Location(-1. 1)}. 0). System. ++i) { Location l = neighbour_list[i].legalpos(l) && ns. } int worldsize = Integer. for (int i = 0. int steps = Integer.err. worldsize)).println("Usage: Main <worldsize> <steps>"). System. System.add(lo).getentity(l) == null && count_neighbours(l) == 3) ns. new Location(0. 1).println("OO-Life.legalpos(l) && s. new Location(0. -1).getentity(lo) == null) ns. -1). this).setentity(l."). lo). society s = new society(new Location(worldsize.add(lo). new Location(1.

println( "Time elapsed: " + (endtime.timetick(). } Date endtime = new Date(). y < worldsize.nextFloat() * 10) < 4) { Location l = new Location(x.starttime. new cell(s. l)). // Uncomment this line to make s.out. } } 153 .Random r = new Random(new Date(). i < steps.setentity(l.0 + " sec. x++) for (int y = 0. y++) { if ((r. i++) { //s. } } Date starttime = new Date(). y). x < worldsize. s.getTime()).getTime()) / 1000.getTime() . for (int x = 0."). for (int i = 0. System.draw().

cpp The application. int y) : x(x). main. class Society { public: 154 . Society& newSociety) { }. y = l. Location location. virtual void draw()=0. protected: friend Society. struct Location { Location(int x. } int x.h> using namespace std. class Society.0) { }. virtual void action(const Society& oldSociety. class Entity { public: Entity(void) :location(0.h> <assert. }.h> <time. virtual Entity& operator=(Entity&).Appendix E: Tweaked Life Source code Part 1: C++ source code The source code consists of just one source file: main. return *this. int y.cpp /* *********************************** * Life simulation * * by Jacob Marner * * 5-26-99 edited 1-23-02 * *********************************** */ #include #include #include #include <queue> <iostream.x. }. y(y) {} Location& operator=(const Location& l) { x = l.y.

t(starttime).y.j<size. Location size. void timetick(void). area = new Entityptr[sizex*sizey]. Entityptr *area. ///////////////////////////////////////////////////// // Implementation ///////////////////////////////////////////////////// Society::Society(const Location& size. delete[] deathrow. // time since start. } void Society::destroyCells() { for (int j=0.x*k]!=NULL) delete area[j+size.i<deathpointer. int starttime) : size(size). deathrow = new Entityptr[sizex*sizey].Society(const Location& size. int t.x. } deathpointer=0.y.j++) for(int k=0.k<size. void setEntity(const Location& pos. int starttime). deathpointer(0) { int sizex = size. // Free dead cells for (int i=0.k++) if (area[j+size. 155 . Entityptr *deathrow. void destroyCells(void). void registerDeath(Entity* e). typedef Entity* Entityptr. Location getsize() const. int sizey = size.i<sizex*sizey. Entity*).++i) { delete deathrow[i]. }. void draw(void) const. They are not // owned by the society. for (int i=0.x. ~Society(). private: void migrateTo(Society*). Entity* getEntity(const Location& pos) const.x*k]. } Society::~Society() { // Do not free regular cells here. bool legalpos(const Location& pos) const.i++) area[i]=NULL. int deathpointer.

} void Society::timetick(void) { Entity* e. assert(pos. for(y=0.x+size. for(int index=0. assert (size.*newsociety).index++) if((e=area[index])!=NULL) e->action(*this.x.y. } void Society::setEntity(const Location& pos.x.x*y])!=NULL) e->draw().x < size. assert (size. for(x=0. cout << "Time cycle: " << t << endl.index < sizea.x). Society* newsociety = new Society(size. for(x=0.x).x. migrateTo(newsociety).x++) { if((e=area[x+size.y). Entity* e. assert(pos. area[pos.delete[] area. int sizea = size. cout << "+".y++) { cout << "|". e->location = pos.y]=e. assert(pos.y).y.y == ns->size.x<size. void Society::migrateTo(Society* ns) { delete[] area. } Entity* Society::getEntity(const Location& pos) const 156 .y.x < size.x*size.x++) cout << "-".x>=0).y<size. cout << "+" << endl.y>=0). } void Society::draw(void) const { int x. cout << "+" << endl.t+1).x == ns->size.x++) cout << "-". else cout << " ". delete ns. } cout << "|" << endl. Note that the deathrow is not // copied and that the cells are not copied either because // they are not owned by the society.x*pos. } cout << "+".x < size.y < size. Entity* e) { assert(pos. for(x=0. area = ns->area. } // Copy the society.

i++) { 157 .x<size.x*pos. assert(pos.-1.-1}. class Cell : public Entity { public: Cell() : Entity() { } int countNeighbours(const Society& s.y+ny[i]).0.1.x+nx[i]. i<8.setEntity(location. } virtual void draw(void) { cout << "*". const Location& pos) { int count=0.x>=0).{ assert(pos. assert(pos.y<size.x && pos.0.1.y<size.1}. // If a neighbour has 3 neighbours then a new is // born there.0. } Entity& Entity::operator=(Entity& e) { location = e.y. } return count. } bool Society::legalpos(const Location& pos) const { return pos. Society& newSociety) { // If the cell has 2 or 3 neighbours it survives int n = countNeighbours(oldSociety.-1. } virtual void action(const Society& oldSociety.0. for (int i = 0. assert(pos.y>=0 && pos. if(s. } //////////////////////////////////////////////////// // Cell definition ///////////////////////////////////////////////////// const int nx[] = {-1. } Location Society::getsize() const { return size.x+size.this).y>=0).location).y).location. for (int i=0.x). } void Society::registerDeath(Entity* e) { deathrow[deathpointer++] = e.1.pos.1. else newSociety.x<size.i<8.getEntity(l)!=NULL) count++.y]. return area[pos.registerDeath(this).-1.getEntity(location)) newSociety.1.i++) { Location l(pos. if ((n>1) && (n<4) && !newSociety.legalpos(l) && s. const int ny[] = {-1. return *this.x>=0 && pos.

x++) for (int y=0.y<worldsize. int endtime = clock().y+ny[i]). return 1.j<steps.draw(). return num.draw(). assert(num>=0). srand(time(NULL)).x<worldsize. for (int j=0. if (oldSociety. int steps = atoi(argv[2]). return 0.new Cell()).setEntity(Location(x.0).new Cell()).timetick(). inline int randomInteger(int high) { int num = (int)((((double)rand())/(((double)RAND_MAX)+1. } int worldsize = atoi(argv[1]). } 158 .setEntity(l.Location l(location." << endl.legalpos(l) && countNeighbours(oldSociety. char* argv[]) { if (argc<3) { cout << "Usage: life <worldsize> <steps>" << endl. } } } }. //mysociety.l)==3 && !newSociety.y++) if (randomInteger(10)<4) mysociety.x+nx[i]. assert(num<=high).worldsize). for(int x=0. Society mysociety(Location(worldsize.0)) * (high+1)).y).getEntity(l)) { newSociety. } mysociety.destroyCells().location. cout << "Time elapsed: " << ((double)(endtime-starttime))/CLOCKS_PER_SEC << " sec. //mysociety. int starttime = clock(). } //////////////////////////////////////////////////////// // Main //////////////////////////////////////////////////////// int main(int argc.j++) { mysociety.

setentity(getLocation(). class Location { Location(int x.x = x.util.lo = lo. Main. // // Takes two integer parameters. // 1. } int x. } void added(Location lo) { x += lo. } void assign(Location lo) { x = lo. y += lo. } abstract class entity { entity(society s. int y) { this. } Location getLocation() 159 .Part 2: Java source code The source code consists of just one source file: Main. this.s = s.y.y = y. y = lo.y. } void action(society S) { S.io.java ////////////////////////////////////////////////////////////////////////////// // Tweaked Life // by Jacob Marner. } char draw() { return 'x'.x. import java. // 2. ////////////////////////////////////////////////////////////////////////////// import java. Location lo) { this. The size of the world as given as the side length of a square world. this.PrintStream.java The application. The number of time steps to run. Feb 2002.*.x. this). int y.

ps.y].x >= 0 && lo.x.y]. ps.y. ++index) { if (e[index] != null) e[index]. int sizea = size. } boolean legalpos(Location lo) { if (lo.y.y >= 0 && lo. } ps.size. ps.x && lo. ps. } void draw() { PrintStream ps = System. x < size.x * lo.x. } void setentity(Location lo. ps.println().println(). size = ns. ps.x * y] != null) ps.x * y].x.y) 160 . for (int index = 0. t = ns.{ return lo.println('|').print(e[x + size.t. } e = ns. entity ent) { e[lo. } entity getentity(Location lo) { return e[lo.println("Turn: " + t). ++x) ps.draw()).print('+').x < size. for (int x = 0.print('+').out. } protected Location lo.x + size. for (int x = 0. for (int y = 0.print('-').print('+').x * size.print(' '). x < size. ps.x * size. } void timetick() { society ns = new society(size). e = new entity[size.e. ++x) if (e[x + size. else ps. } class society { society(Location size) { this. protected society s.x + size.print('+').x * lo. index < sizea.action(ns). ++x) ps.print('|').size = size. for (int x = 0. x < size. ++y) { ps. y < size.y < size.y] = ent.print('-').

Location l = new Location(0. private Location size. new Location(-1. void action(society ns) { int n = count_neighbours(lo).assign(neighbour_list[i]). Location lo) { super(s. m. new Location(0.return true.getentity(l) == null && count_neighbours(l) == 3) ns.legalpos(l) && ns. for (int i = 0.setentity(l. } Location m = new Location(0. 0). if (s.assign(neighbour_list[i]). 1)}. lo).added(lo). -1). } } } public class Main { public static void main(String[] args) 161 . private int count_neighbours(Location lo) { int count = 0. -1). ++i) { m. } return count. } char draw() { return '*'. l)). if (n > 1 && n < 4 && ns. ++i) { l. new Location(-1. -1). 0). l. static final Location[] neighbour_list = { new Location(-1. this).legalpos(l) && s. i < neighbours. new Location(1.getentity(l) != null) ++count. return false. 1). } private entity[] e. i < neighbours.added(lo). } static final int neighbours = 8. new Location(1. private int t. new Location(1. } class cell extends entity { cell(society s. 0). new cell(s. for (int i = 0. if (s. 0). 1).getentity(lo) == null) ns. new Location(0.setentity(lo.

err.out.println("OO-Life.println("Usage: Main <worldsize> <steps>"). for (int i = 0.parseInt(args[1])."). i < steps.getTime()).err. y++) { if ((r. y). System.nextFloat() * 10) < 4) { Location l = new Location(x.println(" cells. int steps = Integer. society s = new society(new Location(worldsize.getTime()) / 1000. } int worldsize = Integer. By Jacob Marner. x++) for (int y = 0. for (int x = 0. The initial cell density is 40%.parseInt(args[0]).getTime() . s. i++) { //s.draw(). y < worldsize. System. System.println( "Time elapsed: " + (endtime.length!=2) { System. Random r = new Random(new Date().setentity(l. System.{ if (args.0 + " sec."). } } 162 .err.starttime.timetick(). System. new cell(s."). x < worldsize. } Date endtime = new Date(). worldsize)). // Uncomment this to print the world to screen.println(" with a sqaure world with room for <worldsize>*<worldsize>"). s. } } Date starttime = new Date().err.err.println(" Will calculate the game of life for <steps> time steps"). l)).

a C++ version and a Java version written in GL4Java and a Java version using Java3D.h Vector. Header file to BoxTree. Feb 2002.cpp.h This file contains a BMP texture loader for OpenGL.cpp Frustum.h> // Note: This loader assumes that int is 32-bit.h" <stdio.cpp A small set of general purpose functions.h> <memory. Main application file.cpp BMPLoader. // // This file contains a BMP texture loader for OpenGL.cpp Matrix. BMPLoader. // ////////////////////////////////////////////////////////////////////////////// // Copyright 2002 Jacob Marner.cpp ////////////////////////////////////////////////////////////////////////////// // Untweaked virtual world (C++ version) // by Jacob Marner. jacob@marner. A 4x4 float matrix class A generic float vector class. Header file to BMPLoader. 163 .h> <string.h general.cpp Vector.dk.cpp A Node in the kd-tree used for the cull scene graph.h BoxTree.h main.h Frustum. Released under LGPL. short is 16-bit and // char is 8-bit.Appendix F: Untweaked 3D Demo The 3D demo exists in three versions. #include #include #include #include #include "BMPLoader. The Java3D demo only exists in one version so the source code will only be found in Appendix G. Header to Vector. Header file to Frustum.cpp BoxTree. Contains most of the implementation of the vector.h> <math. Part 1: C++ source code The source code consists of the following files: BMPLoader.cpp The view frustum.

/* bmfh */ 164 . int c4 = fgetc(file). // UINT bfReserved1. static CompressionType compression. othewise not. static int byteOffset. int c2 = fgetc(file). bytesRead+=1. return c1 + (c2<<8). } // In Windows terms read this structure // typedef struct tagBITMAPFILEHEADER { // UINT bfType. return (unsigned char)c1. BMP_BI_RLE4 }. // The number of bytes read so far static int bytesRead.4 or 8 then // images is paletted. 24. // The file that the BMP is stored in. // Number of bit per pixel. static unsigned char palette[3][256]. // Reads and returns a 32-bit value from the file. static int bitCount. // DWORD bfSize. 8. // Image width in pixels static int width. int c2 = fgetc(file). // The offset from the BITMAPFILEHEADER structure // to the actual bitmap data in the file. // Palette used for paletted images during load. int c3 = fgetc(file).BMP format is stored in little-endian format. static FILE* file. return c1 + (c2<<8) + (c3<<16) + (c4<<24). // Compression enum CompressionType {BMP_BI_RGB=0. // DWORD bfOffBits. BMP_BI_RLE8. bytesRead+=4. // UINT bfReserved2. } // Reads and returns a 16-bit value from the file static short read16BitValue() { int c1 = fgetc(file). 4. bytesRead+=2. // Image height in pixels static int height. static int read32BitValue() { int c1 = fgetc(file). } // Reads and returns a 8-bit value from the file static unsigned char read8BitValue() { unsigned int c1 = fgetc(file). Is 1. If it is 1.// Note: Data in the .

if (read16BitValue()!=1) return LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. // DWORD biClrUsed. // LONG biYPelsPerMeter. // WORD biPlanes. width = read32BitValue(). return LOAD_TEXTUREBMP_SUCCESS. // LONG biHeight. static LOAD_TEXTUREBMP_RESULT readFileHeader(void) { // Read "BM" tag in ASCII. This is irrelevant since we simply // want the bitmap. if (bitCount != 1 && bitCount != 4 && bitCount != 8 && bitCount != 24) return LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT.// } BITMAPFILEHEADER. unsigned int sizeOfInfoHeader = (unsigned int)read32BitValue(). height = read32BitValue(). if (compression != BMP_BI_RGB && compression != BMP_BI_RLE8 && compression != BMP_BI_RLE4) return LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. // DWORD biSizeImage. // Read number of planes. We do not need this since we have the // image size. // LONG biWidth. // DWORD biClrImportant. read32BitValue(). Ignore. // WORD biBitCount. // DWORD biCompression. read32BitValue(). if (sizeOfInfoHeader<40) return LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. // Pixel to device mapping. We do not need this. // Skip the two reserved areas read16BitValue(). // Read the byte offset byteOffset = read32BitValue(). read32BitValue(). // LONG biXPelsPerMeter. read16BitValue(). bitCount = read16BitValue(). } // In windows terms read this struct: //typedef struct tagBITMAPINFOHEADER { /* bmih */ // DWORD biSize. // Read file size. static LOAD_TEXTUREBMP_RESULT readInfoHeader(void) { // We expect this to be at least 40. 165 . According to the specification this // must be 1. // Read image size. read32BitValue(). if (read8BitValue()!=66 || read8BitValue()!=77) return LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. //} BITMAPINFOHEADER. If it is larger we read // some more bytes in the end to be forward compatible. compression = (CompressionType)read32BitValue().

y++) { int index = y*width*3. for (int y=0. // } RGBQUAD. // Apply padding in end of header to be forward compatible. } return LOAD_TEXTUREBMP_SUCCESS. read32BitValue(). We do not need this. sizeOfInfoHeader -= 40.// Read colors used. // BYTE rgbGreen. } return LOAD_TEXTUREBMP_SUCCESS. read8BitValue(). } // The palette follows directly after the // info header // //typedef struct tagRGBQUAD { /* rgbq */ // BYTE rgbBlue. unsigned char byteRead. This will not be needed // in OpenGL so we will ignore this. while (sizeOfInfoHeader>0) { read8BitValue().y<height.i<numColors. int numColors = 1<<bitCount.x++) { if (x%8==0) { byteRead = read8BitValue().x<width. // BYTE rgbRed.j--) palette[j][i] = read8BitValue().i++) { // Read RGB. // Skip reversed byte. // BYTE rgbReserved. for (int j=2. for (int i=0. } 166 . // Read the number of important colors. so it is ignored. } unsigned char color = (byteRead >> (7-(x%8))) & 1. } static LOAD_TEXTUREBMP_RESULT readBitmapData1Bit(unsigned char* bitmapData) { // 1-bit format cannot be compressed if (compression!=BMP_BI_RGB) return LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. sizeOfInfoHeader--. read32BitValue().j>=0. if (bitCount==24) return LOAD_TEXTUREBMP_SUCCESS. bitmapData[index+x*3] = palette[0][color]. bitmapData[index+x*3+2] = palette[2][color]. static LOAD_TEXTUREBMP_RESULT readPalette(void) { // 24-bit images are not paletted. for (int x=0. bitmapData[index+x*3+1] = palette[1][color].

if ((*y)>=height) { *res = LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. } return LOAD_TEXTUREBMP_SUCCESS.j++) { int index = j*width*3. // Uncompressed 4 bit encoding if (compression==BMP_BI_RGB) { for (int j=0. static bool handleEscapeCode(int secondByte. } // This is called after the first byte has been found to be 0 // and the second to be 0-2.j<height. for (int i=0. int* x. 167 . LOAD_TEXTUREBMP_RESULT* res) { if (secondByte==0x00) { // End of line (*x)=0. } else // secondByte=0x02 { // Delta. int* y.i++) { if (i%2==0) { byteValue = read8BitValue(). unsigned char color. unsigned char byteValue. } else if (secondByte==0x01) { // End of bitmap *res = LOAD_TEXTUREBMP_SUCCESS. if (*x>=width || *x<0 || *y>=height || *y<0) { *res = LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. Move drawing cursor. return true. } else { color = byteValue & 0x0F. return true.i<width. } // Draw a 4-bit image. color = byteValue>>4. } } return false. This is only used in RLE-4 and RLE-8 // encoding. static LOAD_TEXTUREBMP_RESULT readBitmapData4Bit(unsigned char* bitmapData) { if (compression!=BMP_BI_RGB && compression!=BMP_BI_RLE4) return LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT.// Pad to 32-bit boundery. *x += read8BitValue(). } (*y)++. return true. while(bytesRead%4 != 0) read8BitValue(). *y += read8BitValue(). Can be uncompressed or RLE-4 compressed.

k++) read8BitValue(). bitmapData[index+x*3+1] = palette[1][color]. // Is this an escape code or absolute encoding? if (firstByte==0) { // Is this an escape code if (secondByte<=0x02) { LOAD_TEXTUREBMP_RESULT res.sizeof(unsigned char)*width*height*3). unsigned char secondByte = read8BitValue(). k<(((width+1)/2)%4). i<secondByte. i++) { if (x>=width) return LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. for (int k=0.} bitmapData[index+i*3] = palette[0][color]. if (i%2==0) { databyte = read8BitValue(). bitmapData[index+i*3+1] = palette[1][color]. } else { // Absolute encoding int index = y*width*3. // Clear bitmap data since it is legal not to // fill it all out. } // Pad to 16-bit word boundery while (bytesRead%2 != 0) read8BitValue().&x. x++. color = databyte >> 4. } else { color = databyte & 0x0F. while(true) { unsigned char firstByte = read8BitValue(). bytesRead=0. int y=0.0. if (handleEscapeCode(secondByte. memset(bitmapData. } bitmapData[index+x*3] = palette[0][color]. bitmapData[index+x*3+2] = palette[2][color]. } } // RLE encoded 4-bit compression if (compression==BMP_BI_RLE4) { // Drawing cursor pos int x=0. unsigned char databyte. int color. 168 . bitmapData[index+i*3+2] = palette[2][color].&res)) return res.&y. } // Pad to 32-bit boundery. for (int i=0.

int index = y*width*3+x*3.i++) { int index = i*width*3. bitmapData[index] = palette[0][color]. bitmapData[index+j*3] = palette[0][color]. memset(bitmapData. } // go to next alignment of 4 bytes. if (compression==BMP_BI_RGB) { // For each scan line for (int i=0.sizeof(unsigned char)*width*height*3).j++) { int color = read8BitValue().k++) read8BitValue(). int color. bitmapData[index+1] = palette[1][color]. k<(width%4). perform data decode for next // length of colors int color1 = secondByte >> 4.0. 169 .i++) { if (x>=width) return LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT.} } else { // If not absolute or escape code. bitmapData[index+2] = palette[2][color]. else color = color2. Can be uncompressed or RLE-8 compressed. for (int k=0. bytesRead=0. } // Read 8-bit image. if (i%2==0) color = color1. // Clear bitmap data since it is legal not to // fill it all out.i<firstByte. } } if (compression==BMP_BI_RLE8) { // Drawing cursor pos int x=0. } } } } return LOAD_TEXTUREBMP_SUCCESS. bitmapData[index+j*3+1] = palette[1][color]. int color2 = secondByte & 0x0F.i<height. x++. for (int i=0. bitmapData[index+j*3+2] = palette[2][color]. static LOAD_TEXTUREBMP_RESULT readBitmapData8Bit(unsigned char* bitmapData) { if (compression!=BMP_BI_RGB && compression!=BMP_BI_RLE8) return LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. int y=0. for (int j=0.j<width.

i<firstByte.i++) { int index = i*width*3. bitmapData[index+x*3+1] = palette[1][color]. bitmapData[index+j*3+1] = read8BitValue(). } } } } return LOAD_TEXTUREBMP_SUCCESS.&x. x++. bitmapData[index+1] = palette[1][secondByte]. } else { // Absolute encoding int index = y*width*3.i<height. perform data decode for next length of colors for (int i=0. } // Load a 24-bit image.while(true) { unsigned char firstByte = read8BitValue(). for (int i=0. } // Pad to 16-bit word boundery if (secondByte%2 == 1) read8BitValue(). bitmapData[index] = palette[0][secondByte].&res)) return res. Cannot be encoded. for (int i=0. if (compression!=BMP_BI_RGB) return LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. bitmapData[index+j*3] = read8BitValue(). i++) { if (x>=width) return LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. if (handleEscapeCode(secondByte. bitmapData[index+x*3] = palette[0][color]. Verify this. static LOAD_TEXTUREBMP_RESULT readBitmapData24Bit(unsigned char* bitmapData) { // 24-bit bitmaps cannot be encoded. bitmapData[index+x*3+2] = palette[2][color]. bitmapData[index+2] = palette[2][secondByte].i++) { if (x>=width) return LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. i<secondByte. unsigned char secondByte = read8BitValue(). // Is this an escape code or absolute encoding? if (firstByte==0) { // Is this an escape code if (secondByte<=0x02) { LOAD_TEXTUREBMP_RESULT res.&y. x++. int index = y*width*3+x*3.j<width. } 170 . int color = read8BitValue().j++) { bitmapData[index+j*3+2] = read8BitValue(). for (int j=0. } } else { // If not.

// Append . default: // 24 bit. case 4: return readBitmapData4Bit(bitmapData). // Read Info Header if (!res) res=readInfoHeader(). while(bytesRead<byteOffset) read8BitValue().filename). if (!file) res = LOAD_TEXTUREBMP_COULD_NOT_FIND_OR_READ_FILE. } return LOAD_TEXTUREBMP_SUCCESS. // Open file for buffered read. return readBitmapData24Bit(bitmapData). file = fopen(str. for (int k=0. strcat(str. bytesRead=0. readBuffer). k<((width*3)%4). unsigned char** bitmapData) { *bitmapData = NULL. case 8: return readBitmapData8Bit(bitmapData).// go to next alignment of 4 bytes. } // Read File Header if (!res) res=readFileHeader(). } } // Load the BMP.". } static LOAD_TEXTUREBMP_RESULT readBitmapData(unsigned char* bitmapData) { // Pad until byteoffset. // If successful *bitmapData will contain a pointer to the data. Most images will need no padding // since they already are made to use as little space as // possible. strcpy(str. LOAD_TEXTUREBMP_RESULT loadBMP(const char* filename. switch(bitCount) { case 1: return readBitmapData1Bit(bitmapData). LOAD_TEXTUREBMP_RESULT res = LOAD_TEXTUREBMP_SUCCESS.k++) read8BitValue(). Assumes that no extension is appended to the filename. 171 ."rb").bmp extension char* str = new char[strlen(filename)+5]. // The data reading procedure depends on the bit depth. char readBuffer[BUFSIZ].bmp"). if (!res) { setbuf(file. if (!str) return LOAD_TEXTUREBMP_OUT_OF_MEMORY.

bmp") || !strcmp(withext+i. } // Removes the . if (file) fclose(file). 1).j<i.i--) { if (!strcmp(withext+i. if (*bitmapData && res) delete[] *bitmapData.BMP")) { for (int j=0.j++) { result[j] = withext[j]. // Only clean up bitmapData if there was an error. str). char* str = new char[strlen(filename)]. return res. glPixelStorei(GL_UNPACK_ALIGNMENT. glGenTextures(1.i>=0. glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT). char* result) { for(int i=strlen(withext)-1. } result[i]='\0'. *textureName). static void removeBMPextension(const char* withext. if (!str) return LOAD_TEXTUREBMP_OUT_OF_MEMORY. } } } // The main function. if (!bitmapData) res = LOAD_TEXTUREBMP_OUT_OF_MEMORY.textureName).// Read Palette if (!res) res=readPalette(). This is the only one that is exported.".". return. GLuint *textureName. GLint internalformat) { LOAD_TEXTUREBMP_RESULT res. LOAD_TEXTUREBMP_RESULT loadOpenGL2DTextureBMP(const char* filename. } // Clean up delete[] str. if (!res) { // The bitmap data we are going to hand to OpenGL *bitmapData = new unsigned char[width*height*3]. removeBMPextension(filename.bmp extension of the filename if it exists. } if (!res) { // Read Data res=readBitmapData(*bitmapData). glBindTexture(GL_TEXTURE_2D. 172 .

LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. delete[] str. jacob@marner.h> // The following is the function return type.textureName). You proably used a internal // format not accepted by your OpenGL implementation or you may have run // out of available texture names. width. // load texture into OpenGL with mip maps and scale it if needed.h> and <GL/glu. GL_RGB. Feb 2002. If an error occured. } BMPLoader. If you use Windows but not GLUT // remember to include <windows. // The system ran out of heap memory during the texture load. internalformat. } // Clean up. LOAD_TEXTUREBMP_OUT_OF_MEMORY}. Use this to // get information about how the loading operation went. return res. // The file does not comply with the specification. // <GL/gl.h ////////////////////////////////////////////////////////////////////////////// // Untweaked virtual world (C++ version) // by Jacob Marner. if (!res) { gluBuild2DMipmaps(GL_TEXTURE_2D. // The bitmap will be loaded without borders and all mip maps will be // generated automatically. The status of the operation will be returned as the return value. remove the following // include line and include the standard OpenGL headers instead. texturename is undefined. // // // // Loads the specified BMP image and stores it into a OpenGL 2D texture. LOAD_TEXTUREBMP_COULD_NOT_FIND_OR_READ_FILE.bitmapData). glPopClientAttrib().// load bmp unsigned char* bitmapData. Released under LGPL. If you need the bitmap for anything else than 173 .&bitmapData). height. if (res) glDeleteTextures(1. #include <GL/glut. whose name will be returned in textureName. enum LOAD_TEXTUREBMP_RESULT { // The texture loading operation succeeded. // OpenGL could not accept the texture. res = loadBMP(str.h> also. LOAD_TEXTUREBMP_SUCCESS=0.dk. LOAD_TEXTUREBMP_OPENGL_ERROR. GL_UNSIGNED_BYTE.h>. #ifndef BMP_LOADER_HEADER #define BMP_LOADER_HEADER // Does you program not use GLUT? If not. // // A OpenGL BMP loader // ////////////////////////////////////////////////////////////////////////////// // Copyright 2002 Jacob Marner. if (bitmapData) delete[] bitmapData. } if (glGetError()!=GL_NO_ERROR) { res = LOAD_TEXTUREBMP_OPENGL_ERROR. // The file could not be found or it could not be opened for reading.

const Frustum& frustum) { // Find corner furthest from plane in normals direction. // If not it is fully inside. boundingBox[nz]. bool Node::basicCullTest(int plane. float splitvalue). float splitvalue). // Check if corner furthest from plane in normals direction is outside. #endif BoxTree. Return value: [out] Status of operation. 174 . n. Must be Windows .// textures. int nx = frustum. frustum. For the filename string given as parameter the extension is optional .x such that all with n.h" // Reorder the box data in place according // values below split value comes first.cpp ////////////////////////////////////////////////////////////////////////////// // Untweaked virtual world (C++ version) // by Jacob Marner.y.z. int to position.bmp extension.points[plane]. } static bool isInside. float y.y. boundingBox[ny].y.normals[plane]. Defaults to GL_RGB.normals[plane])) { // Check if corner closest to plane in normal's direction is outside. // // A Node in the kd-tree used for the cull scene graph. The actual file *must* use the . boundingBox[1-ny].normals[plane]. [in/out] textureName: Pass a pointer to an already allocated GLuint and if the operation success then this data will point to the texture name of the texture object loaded. static inline bool isOutside(float x. // ////////////////////////////////////////////////////////////////////////////// #include <iostream.z >= 0 ? 1 : 0. the box is fully outside. const Vector& normal) { return normal. const Vector& point. or you need borders you will have to edit the source code. int static int reordery(BoxData boxData[]. int nz = frustum. if (isOutside(boundingBox[nx]. frustum.x. If the return value is not LOAD_TEXTUREBMP_SUCCESS then this value is undefined. float z.y >= 0 ? 1 : 0. float splitvalue). int static int reorderz(BoxData boxData[]. This can immediately be used with glBindTexture() to use the texture during drawing.BMP format. // If no.h" #include "Matrix.x >= 0 ? 1 : 0.h> #include "BoxTree. the box is intersecting the fustrum border. static int reorderx(BoxData boxData[]. [in] internalformat: The internal format of the loaded texture. Feb 2002. int ny = frustum.normals[plane]. // If yes. if (isOutside(boundingBox[1-nx].if it is not there it is appended automatically.z)-point)>=0. LOAD_TEXTUREBMP_RESULT loadOpenGL2DTextureBMP(const char* filename.dot(Vector(x. // // // // // // // // // // // // // // // // Parameters: [in] filename: The name of the texture file. If you later want to delete the loaded texture call glDeleteTextures() on the returned texture name. n. Is passed directly to glTextImage2D(). GLint internalformat = GL_RGB).x. GLuint* textureName.

// 2. } // We are now in doubt whether the box should be drawn.frustum)) return. return. We remember what plane // was used to cull this the last time and we start with // that. frustum. // 3. for (plane=0. if (basicCullTest(plane.frustum)) { lastPlaneCulled = plane. plane++) { if (plane==lastPlaneCulled) continue. plane<6.frustum. } } // Is the box fully within the frustum? If yes.points[plane]. int plane. if not go to children nodes. } else { leftChild->render(frustum). rightChild->render(frustum). These are used to do a bounding sphere // culling. if (leftChild==NULL) { glCallList(displayList). simply render the // display list. } return false. } // We do a frustum culling algorithm. } else isInside=false. // If it is a leaf draw it all.boundingBox[1-nz]. void Node::render(const Frustum& frustum) { isInside = true. Bounding spheres for the frustum and bounding box // is precalculated.z. if (isInside) { glCallList(displayList).getLengthSquared() > frustum. Plane to point comparison to see of the box // is entirely outside the plane. Temporal coherance culling.normals[plane])) { return true. if (basicCullTest(lastPlaneCulled. // Note: We do not use the octant test to reduce the plane // comparisons needed to 3 since that requires a symmetric // frustum. frustum. if ((boxCenter . } } 175 . We only test to points // per bounding box for each plane. return. consisting of // 1.center).radiusSquared + boxRadiusSquared) return.

x + boxMaxDistFromCenter.z .boxMaxDistFromCenter.posy. candidate = boxData[c]. float yspan = boundingBox[1].z) boundingBox[1].WORLD_MAX_Y.boxMaxDistFromCenter. glVertex3f(w. if (candidate < boundingBox[0].texy). boundingBox[1] = Vector(0.position. rightChild(NULL). if (candidate < boundingBox[0].y + boxMaxDistFromCenter. v.normalize(). candidate = boxData[c]. // (Boxes is centered about position and has a distance // from center of max sqrt(2*pow(scale.y) boundingBox[0].y = candidate.y . lastPlaneCulled(0) { boundingBox[0] = Vector(WORLD_MAX_X. candidate = boxData[c].boundingBox[0]).position.2)).MultLeftMatrixIgnoreW(t). if (candidate < boundingBox[0].z).y .y.v.y) boundingBox[1]. // If n is more than max_boxes if (n>MAX_BOXES_PER_LEAF) { float xspan = boundingBox[1].x. boxRadiusSquared = (boxCenter .position. w.position. int n) : leftChild(NULL).x = candidate. candidate = boxData[c].z + boxMaxDistFromCenter.boundingBox[0].boundingBox[0].y.position. float texx.y = candidate.z).x . float x. } // The given normal is assumed to be normalized already. float texy. float candidate.x) boundingBox[0].x . Vector w(posx.x) boundingBox[1].boundingBox[0].y.w.x = candidate.posz).w. if (candidate > boundingBox[1]. if (candidate > boundingBox[1]. if (candidate > boundingBox[1].z) boundingBox[0]. v. glNormal3f(v. void Node::transformAndSetNormal(const Matrix& t.v. float posz) { glTexCoord2f(texx. float z) { Vector v(x.z).getLengthSquared().WORLD_MAX_Z).void Node::transformAndDrawVertex(const Matrix& m. ++c) { float boxMaxDistFromCenter = sqrt(2*pow(boxData[c].2)) ) for (int c=0. } boxCenter = (boundingBox[1]-boundingBox[0])/2 + boundingBox[0]. candidate = boxData[c]. float posx. float zspan = boundingBox[1]. // Find and set bounding box by iterating through boxes. c<n. float y. } Node::Node(BoxData boxData[].z. candidate = boxData[c].z = candidate. 176 .0).y.x.boxMaxDistFromCenter.x. displayList(0).MultLeftMatrix(m).z = candidate.scale.z . float posy.position.0.

splitn = reorderx(boxData.-1. GL_COMPILE).0f. splitn = reorderz(boxData.1). rightChild = new Node(&(boxData[splitn]).x + xspan/2.rotate(boxData[i]. glEndList().rotation. // Reorder box data according to center on the max axis.0.z.0f.scale. m.1.center).x. // Left side transformAndSetNormal(t. // Reorder box data according to center on the max axis. n-splitn). float scale = boxData[i]. 177 . This display list // must be executed within a begin() end() block that was started // with GL_QUAD_STRIP displayList = glGenLists(1).0. if (xspan >= yspan && xspan >= zspan) { // Find center along max axis float center = boundingBox[0].loadIdentity().0f.center). m.++i) { Matrix m.1. GL_COMPILE). } } // Create nodes for each child leftChild = new Node(boxData.0).1. // Initialize display list that calls children display list displayList = glGenLists(1).int splitn. t.center).boxData[i].1).0. glCallList(rightChild->displayList).invert()). glNewList(displayList.scale).0). Matrix t(m).rotation. m.z). ASSERT(displayList). transformAndDrawVertex(m.x. } else { // Find center along max axis float center = boundingBox[0].rotate(boxData[i]. boxData[i].-1.1.position.1.-1.z + zspan/2. splitn = reordery(boxData.1).n. m. } else { if (yspan >= zspan) { // Find center along max axis float center = boundingBox[0].0. // Reorder box data according to center on the max axis.0.rotation.position.y. glNewList(displayList. for (int i=0.scale.rotate(boxData[i].-1. m.1.scale(scale. VERIFY(t. m.position.n.y + yspan/2.i<n. ASSERT(displayList). splitn).transpose().0.0).y. transformAndDrawVertex(m.translate(boxData[i].n. } else { // Initialize display list to draw the boxes. glCallList(leftChild->displayList).

0.-1).0.-1).0.-1).-1.0. transformAndDrawVertex(m. transformAndDrawVertex(m.1. \ int j = r+1.0. } #define REORDER(X) \ static int reorder ## X(BoxData boxData[].0.-1). transformAndDrawVertex(m.1.0. \ \ int i = p-1.-1).-1.-1.1.0).0.1.-1. transformAndDrawVertex(m. } } Node::~Node(void) { if (leftChild) { delete leftChild.1. ASSERT(rightChild).1. transformAndDrawVertex(m.1.-1.1. transformAndDrawVertex(m.-1.1.1.1.0).-1.1.0.1. transformAndDrawVertex(m.1.1.1.0. transformAndDrawVertex(m.1.-1.1.0.-1. } glEndList().0.1).1. transformAndDrawVertex(m.1). \ \ while (j>=p && boxData[j].0. transformAndDrawVertex(m.1.0.-1.1.1.-1.1).1.0. transformAndDrawVertex(m.0.0.-1.1. \ int p = 0.1.-1).1. ## X>splitvalue) \ 178 . delete rightChild.1).1). transformAndDrawVertex(m.transformAndDrawVertex(m.-1.1). float splitvalue) \ { \ if (n<1) \ return 0.1.1). transformAndDrawVertex(m.0. transformAndDrawVertex(m.1.1.1. // Top side transformAndSetNormal(t. transformAndDrawVertex(m. // Right side transformAndSetNormal(t.0.1).1. // Back side transformAndSetNormal(t.-1.1).1.0.-1.0.position.0.-1).0.0.-1. } glDeleteLists(displayList. transformAndDrawVertex(m.1.1).-1.-1).-1.1. transformAndDrawVertex(m. transformAndDrawVertex(m.0.0.1. \ \ int r = n-1.1.1.-1. \ j--.1.0.-1).-1).1. // Under side transformAndSetNormal(t.0.0).1.1).1.1. \ while (true) \ { \ i++.-1).1.1. // Front side transformAndSetNormal(t.1.0.1.-1. transformAndDrawVertex(m.-1).-1). transformAndDrawVertex(m.-1.1).0. transformAndDrawVertex(m.-1. int n.

h ////////////////////////////////////////////////////////////////////////////// // Untweaked virtual world (C++ version) // by Jacob Marner. if (j<=p) j=p. // // A Node in the kd-tree used for culling. ## X<splitvalue) i++. }. } } } REORDER(x) REORDER(y) REORDER(z) \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ BoxTree. boxData[j] = temp.position. 179 . int n). n is the size of the array. class Node { public: // This method may reorder the ordering of the // box data array. // ////////////////////////////////////////////////////////////////////////////// #ifndef BOXTREE_LIBRARY #define BOXTREE_LIBRARY #include #include #include #include "Vector.h" "Matrix. boxData[i] = boxData[j].j--. } else { while (j<r && boxData[j]. // First min box then max box Vector boundingBox[2]. Node(BoxData boxData[]. ## X<splitvalue) j++.h" <GL/glut. Feb 2002. while(i<=r && boxData[i].h> #define WORLD_MAX_X 100 #define WORLD_MAX_Y 100 #define WORLD_MAX_Z 100 #define MAX_BOXES_PER_LEAF 10 struct BoxData { float scale. return j.h" "Frustum. Vector position.position. ~Node(). if (i<j) { BoxData temp = boxData[i]. Vector rotation.

// Set normals for near and far planes. // // The view fustrum. Vector centerOfNearPlane = cameraPos + cameraDirection * pnear. float posy. int lastPlaneCulled. void transformAndSetNormal(const Matrix& m. Node* leftChild. cameraDirection. cameraUp. float texy. points[5] = cameraPos + cameraDirection * pfar. GLdouble pnear. We have one point on the plane as the cameraPos.i++) points[i] = cameraPos. 2. private: void transformAndDrawVertex(const Matrix& m. // ////////////////////////////////////////////////////////////////////////////// #include "Frustum. GLdouble pfar) { // Make sure that all the direction vectors are unit vectors. float texx. Vector cameraUp. The general scheme is: 1. float z). float posz). GLdouble bottom. By moving along the near plane with the given amount we get another point in the plane. It is now paralell to the near plane. Node* rightChild. // The camera is in the plane of the sides. GLdouble left.normalize(). // Normal points towards camera normals[5] = cameraDirection.right) or the perpendular of the upvector (top. bool basicCullTest(int plane. for (i=0. // Make sure that the up vector is perpendular to the // cameraDirection. float posx. normals[4] = -cameraDirection.void render(const Frustum& frustum). 3. crossed with the upvector (left. cameraUp = cameraDirection.cross(cameraUp.cpp ////////////////////////////////////////////////////////////////////////////// // Untweaked virtual world (C++ version) // by Jacob Marner. // Normal points away from camera // // // // // // // Calculate normals for sides. // Points for the near anf far planes are found by intersection // viewing direction line with plane. const Frustum& frustum). float boxRadiusSquared.normalize(). Vector boxCenter. GLdouble right. float x. int i. Feb 2002. float y. points[4] = centerOfNearPlane. GLuint displayList.cross(cameraDirection)).bottom) give us the normal of the plane.h" Frustum::Frustum(Vector cameraPos. }. GLdouble top. Vector cameraDirection. 180 . #endif Frustum.i<4. These two points can.

cameraPos).cross(cameraUp) . // Calculate normal for bottom side Vector bottomNearPoint = centerOfNearPlane + cameraUp * bottom.h ////////////////////////////////////////////////////////////////////////////// // Untweaked virtual world (C++ version) // by Jacob Marner.-left). GLdouble top.cross(topNearPoint .cross(cameraUp) * right + centerOfNearPlane.far.cameraPos). // Calculate normal for top side Vector topNearPoint = centerOfNearPlane + cameraUp * top.// Calculate normal for left side Vector leftNearPoint = cameraDirection.h" class Frustum { public: // points and normals for planes in this order: // left.cross(cameraUp).(leftNearPoint . GLdouble right. float radiusSquared.getSquaredLength(). normals[1] = (rightNearPoint . Vector center. Feb 2002.cameraPos). #endif 181 .bottom. Vector extremeFarCorner = ((extremeNearCorner .cross(cameraUp). // The first three args is information about the camera in the world.cameraDirection. GLdouble bottom. GLdouble pfar).points[4])/2 + points[4]. // // The view fustrum // ////////////////////////////////////////////////////////////////////////////// #ifndef FRUSTUM_LIBRARY #define FRUSTUM_LIBRARY #include <GL/glut. // The last 6 is the parameters given to glFrustum().cross(cameraUp) . // Calculate normal for right side Vector rightNearPoint = cameraDirection.near.cross(cameraUp) * max(right. Frustum(Vector cameraPos.center).cross(cameraUp) * left + centerOfNearPlane.cameraPos).cameraPos) / pnear) * pfar + cameraPos. Vector cameraUp. // Calculate the center of the frustum center = (points[5] . } Frustum. radiusSquared = (extremeFarCorner . normals[3] = cameraDirection.h> #include "Vector.top. Vector normals[6]. Vector cameraDirection.-bottom) + cameraDirection. normals[0] = . Vector extremeNearCorner = centerOfNearPlane + cameraUp * max(top.right. }.cross(bottomNearPoint . normals[2] = . GLdouble left. Vector points[6]. GLdouble pnear.

} // Return a random int number between low and high. return num.const Type b) { return a<b ? a : b. } #endif // Return the absolute value of a template <class Type> Type Abs(const Type a) { return a<0 ? -a : a. } int num = (int)((((double)rand())/(((double)RAND_MAX)+1.h> <stdlib.h> <string.general. // ////////////////////////////////////////////////////////////////////////////// #ifndef GENERAL_LIB #define GENERAL_LIB #include #include #include #include <assert. float high) 182 . inline int randomInteger(int low. both inclusive. Feb 2002. low = high. int high) { if (low>high) { int temp = low.0)) * (high-low+1) + low). high = temp. ASSERT(num<=high). const Type b) { return a>b ? b : a. ASSERT(num>=low).h> <stdio. } // Return a random floating point number between low and high // both inclusive inline float randomFloat(float low. // // A small set of general purpose functions. } #endif #ifndef min template <class Type> Type min(const Type a.h ////////////////////////////////////////////////////////////////////////////// // Untweaked virtual world (C++ version) // by Jacob Marner.h> #ifdef _DEBUG #define ASSERT(X) assert(X) #else #define ASSERT(X) #endif #ifdef _DEBUG #define VERIFY(X) assert(X) #else #define VERIFY(X) X #endif #ifndef max template <class Type> Type max(const Type a.

low = high.strlen(label))) { return true. sscanf(s. inline float getFloatArgument(const char* label. char* argv[].i<argc. Feb 2002. int argc.cpp ////////////////////////////////////////////////////////////////////////////// // Virtual World (C++ version) // by Jacob Marner. int argc.argv[i]. } } return defaultvalue.i++) { if (!strncmp(label.0)) * (high-low) + low). If label was not found return defaultvalue. inline int getIntegerArgument(const char* label. float defaultvalue) { for(int i=0. ASSERT(num<=high). If label was not found return defaultvalue.argv[i]. return num. int argc.&f).{ if (low>high) { float temp = low.argv[i]. high = temp. } // Search a set of command line arguments for one beginning with label. } // Search a set of command line arguments for one beginning with label. return f.strlen(label))) { char* s = argv[i]+strlen(label). // If found parse the ramainder of the string as a float and return // the value.i++) { if (!strncmp(label. char* argv[]. int defaultvalue) { for(int i=0. float f. // If it exist return true. } } return false."%f". } #endif main.i<argc.strlen(label))) { return atoi(argv[i]+strlen(label)). // If found parse the ramainder of the string as an integer and return // the value.i++) { if (!strncmp(label. } // Search a set of command line arguments for one beginning with label. ASSERT(num>=low). } float num = (float)((((double)rand())/(((double)RAND_MAX)+1. char* argv[]) { for(int i=0. } } return defaultvalue. inline bool getBooleanFlag(const char* label. 183 .i<argc.

glLoadIdentity(). height).5*((double)height/(double)width). // -boxes:<int> : The number of boxes to generate. Default: 1000.h> "BoxTree. glMatrixMode(GL_MODELVIEW). frustumBottom.frustumBottom.h" "Frustum. Default: 3000. glFrustum(frustumLeft.5*((double)height/(double)width).h" <time." << endl. void reshape(int width.frustumRight.// // The application takes two optional parameters: // -frames:<int> : The number of frames to run the animation. clock_t starttime. } void stopTimer(void) { float timeelapsed = ((float)(clock() .h> #endif Node* root.5.h" "Vector.h" #ifdef _WIN32 #include <windows. // ////////////////////////////////////////////////////////////////////////////// #include <GL/glut. frustumNear. int height) { glViewport(0.h> #include #include #include #include #include #include #include "general." << endl. int numFrames. frustumBottom = -0.5. GLuint tex. void startTimer(void) { starttime = clock().5. int frame = 0. frustumRight = 0. } void display(void) 184 . frustumTop = 0. frustumTop. cout << "Elapsed time: " << timeelapsed << " sec. frustumFar = 40. width.frustumNear.starttime)) / CLOCKS_PER_SEC.frustumFar).h" "BMPLoader. 0. frustumLeft = -0. frustumRight. glMatrixMode(GL_PROJECTION). cout << "Average frame rate: " << ((float)numFrames)/timeelapsed << " fps.h> <iostream. frustumTop. frustumNear = 0. } GLdouble GLdouble GLdouble GLdouble GLdouble GLdouble frustumLeft.

0 }. float radius = 20. frustumRight. upVector.0). exit(0).x. frame++. tex). Vector upVector(0. glLightfv(GL_LIGHT0.y. BoxData* boxData = new BoxData[numBoxes].GL_AMBIENT. 1.0}.y+cameraDirection.z. srand(time(NULL)). // Generate numBoxes box data put them in array. 0. glMaterialfv(GL_FRONT.specular_light).0. 1. cameraPos. } } void idle(void) { glutPostRedisplay().0f)).y.0f+radius*cos(angle).mat_shininess).0+radius*sin(angle)). cameraPos. 1.GL_SPECULAR. frustumBottom.light_position).14f/2. root->render(frustum). Vector cameraPos(WORLD_MAX_X/2. Frustum frustum(cameraPos. // Move camera in circle around center of world. -1. 1.0f.0.y. glEnd().0. 1.0. upVector. upVector.0. glMaterialfv(GL_FRONT.{ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT).GL_SHININESS.0.0.0.0}.1.14f/2. frustumFar).mat_specular).z+cameraDirection. For each one // store scale(1). Vector cameraDirection(cos(angle+3. glBindTexture(GL_TEXTURE_2D.cameraPos.GL_SPECULAR.diffuse_light).GL_DIFFUSE. GLfloat mat_shininess[] = { 50. WORLD_MAX_Z/2.x.0 }.0. GLfloat mat_specular[] = { 1. glLightfv(GL_LIGHT0. cameraDirection. glBegin(GL_QUADS). 1.ambient_light). float angle = (float)frame / 600.0.0. print results and exit. 1.0. gluLookAt(cameraPos. glLightfv(GL_LIGHT0. GLfloat ambient_light[] = {1. 1. glLightfv(GL_LIGHT0.0}.sin(angle+3. rotation(3). 0.x+cameraDirection.upVector.x. 0.cameraPos.0 }. pos(3). 0. // If final frame has been drawn stop timer. 1. GLfloat light_position[] = {0. 185 .0. frustumLeft. //glCallList(root->displayList). 1. WORLD_MAX_Y/2. cameraPos. GLfloat diffuse_light[] = {1.0.z).0f). glutSwapBuffers(). glLoadIdentity(). Generate each number // randomly.GL_POSITION.0.0. } void init(int numBoxes) { int i.z.0f. if (frame>=numFrames) { stopTimer(). frustumTop. GLfloat specular_light[] = {0. frustumNear.

rotation.360). boxData[i].z= randomFloat(0. numFrames = getIntegerArgument("-frames:". 0). boxData[i].WORLD_MAX_Y).x= randomFloat(0.WORLD_MAX_X). boxData[i]. 100).&tex. } // // // // // // Create a tree structure.bmp".5). // Turn on z-buffer for hidden surface removal glEnable(GL_DEPTH_TEST). int numBoxes = getIntegerArgument("-boxes:".position.360). Each node contains are display list printing all contents of that node as one diplaylist. boxData[i]. The leaf nodes push viewmodel matrix.i<numBoxes. glutInitWindowPosition(100. } int main(int argc.rotation. // Enable back face culling. argv).z= randomFloat(0. boxData[i]. atexit(exitfunc).scale= randomFloat(0. pos. glCullFace(GL_BACK).x= randomFloat(0. 480). root = new Node(boxData.1.y= randomFloat(0. 1000).WORLD_MAX_Z). Each node has bounding box information and zero or two children. 100). 0.360). glHint(GL_PERSPECTIVE_CORRECTION_HINT. // Tell OpenGL to use flat shading glShadeModel(GL_FLAT).rotation.for (i=0. boxData[i].position.++i) { boxData[i]. glEnable(GL_LIGHTING).1. 0. GL_NICEST). argc. argv. // Create a 640x480 window glutInitWindowSize(640.position.argv. rotation (as one matrix!) draw the stripped box and then pop matrix again. argc. scale. // Load texture if (loadOpenGL2DTextureBMP("glow.y= randomFloat(0. delete[] boxData. numBoxes). glEnable(GL_LIGHT0). glutCreateWindow("C++ OpenGL benchmark"). char *argv[]) { /////////////////////////////////////////////////// // Initialize OpenGL /////////////////////////////////////////////////// glutInit(&argc. // Enable Phong lighting model and one global // directional light. Inner nodes use call list to sub node display lists. } void __cdecl exitfunc( void ) { delete root.GL_RGB)) 186 . glClearColor(0. // Tell OpenGL to use double buffering and // 16/32 bit colors (whatever is the default) glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE). glEnable(GL_CULL_FACE).

0f}. wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT").0f. 0. frustumFar * 0.GL_TEXTURE_MAG_FILTER. // Enable trilinear filtering. GL_LINEAR). glHint(GL_FOG_HINT. GL_DONT_CARE). glTexParameteri(GL_TEXTURE_2D. } Matrix. // Blend lighting a texture effects. GL_MODULATE).GL_LINEAR). #ifdef _WIN32 // Turn off vertical screen sync under Windows. ASSERT(wglSwapIntervalEXT). wglSwapIntervalEXT(0).85). // (I.GL_TEXTURE_MIN_FILTER.0f. // // A 4x4 float matrix class // ////////////////////////////////////////////////////////////////////////////// #ifndef MATRIX_CLASS #define MATRIX_CLASS #include <math. it uses the WGL_EXT_swap_control extension) // On under systems we just live with the default. typedef BOOL (WINAPI *PFNWGLSWAPINTERVALEXTPROC)(int interval).0f.h ////////////////////////////////////////////////////////////////////////////// // Untweaked virtual world (C++ version) // by Jacob Marner.1415926535f/180.GL_TEXTURE_ENV_MODE. PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL. // Enable fogging glEnable(GL_FOG).e. glutMainLoop().h> template<class Type> inline float toRadians(Type deg) { return deg*3. } // Enable texture mapping glEnable(GL_TEXTURE_2D).35). 0. fogColor). GLfloat fogColor[4] = {0. #endif // Create data and display lists init(numBoxes). glFogf(GL_FOG_START. glTexParameteri(GL_TEXTURE_2D. glutIdleFunc(idle). glFogf(GL_FOG_DENSITY. glTexEnvf(GL_TEXTURE_ENV. startTimer(). return 0. frustumFar). glFogi(GL_FOG_MODE.GL_LINEAR_MIPMAP_LINEAR). glutDisplayFunc(display). } 187 . 0. glFogf(GL_FOG_END. glFogfv(GL_FOG_COLOR.0f. 0.{ cerr << "Error loading texture. exit(1). Feb 2002." << endl. // Register call back functions glutReshapeFunc(reshape).

class Matrix { public: float m[16]. m[15] = m[3] * n[12] + m[7] * n[13] + m[11] * n[14] + m[15] * n[15].ma. 188 .float m2. return *this. m[1]=m[2]=m[3]=m[4]=m[6]=m[7]=m[8]=m[9]=m[11]=m[12]=m[13]=m[14]=0.float m12. t[2] = m[2] * n[0] + m[6] * n[1] + m[10] * n[2] + m[14] * n[3].float m3. m[10] = m10. m[9] = m9.float m11. float m5.16*sizeof(float)). m[12] = m[0] * n[12] + m[4] * n[13] + m[8] * n[14] + m[12] * n[15]. } Matrix& operator=(const Matrix& ma) { memcpy(m.12*sizeof(float)). m[2] = m2. m[12] = m12.float m4. float m15) { m[0] = m0. t[11] = m[3] * n[8] + m[7] * n[9] + m[11] * n[10] + m[15] * n[11]. m[8] = m8.m. m[13] = m[1] * n[12] + m[5] * n[13] + m[9] * n[14] + m[13] * n[15]. t[5] = m[1] * n[4] + m[5] * n[5] + m[9] * n[6] + m[13] * n[7]. memcpy(m.float m14.t. m[1] = m1.16*sizeof(float)). t[6] = m[2] * n[4] + m[6] * n[5] + m[10] * n[6] + m[14] * n[7]. m[7] = m7.float m7. t[0] = m[0] * n[0] + m[4] * n[1] + m[8] * n[2] + m[12] * n[3]. m[11] = m11. t[10] = m[2] * n[8] + m[6] * n[9] + m[10] * n[10] + m[14] * n[11]. t[4] = m[0] * n[4] + m[4] * n[5] + m[8] * n[6] + m[12] * n[7]. m[14] = m[2] * n[12] + m[6] * n[13] + m[10] * n[14] + m[14] * n[15].m. m[4] = m4.float m1.float m6. Matrix(void) { } void loadIdentity() { m[0]=m[5]=m[10]=m[15]=1. m[14] = m14. t[9] = m[1] * n[8] + m[5] * n[9] + m[9] * n[10] + m[13] * n[11].ma. float t[12]. m[15] = m15. float m10. t[8] = m[0] * n[8] + m[4] * n[9] + m[8] * n[10] + m[12] * n[11].m. m[3] = m3. m[5] = m5. t[1] = m[1] * n[0] + m[5] * n[1] + m[9] * n[2] + m[13] * n[3]. } Matrix(const Matrix& ma) { memcpy(m.float m8. } Matrix(float m0. t[3] = m[3] * n[0] + m[7] * n[1] + m[11] * n[2] + m[15] * n[3]. m[6] = m6. m[13] = m13. } void mult(Matrix &ma) { float *n = ma. t[7] = m[3] * n[4] + m[7] * n[5] + m[11] * n[6] + m[15] * n[7].float m9.float m13.

z/=length. by a rotate */ m[8] * n[2]. } void translate(float x. /* m[12] to m[15] are not changed float t[8].0f) { float length = (float)sqrt(slength). // n[3] not used n[4] = xy*oneminusc-zs. n[6] = yz*oneminusc+xs.c.t. m[10] * n[10]. float x.0f . if (slength!=1. yz = y*z. m[2] *= x. n[9] = yz*oneminusc-xs. m[1] *= x. m[11] * n[6]. m[10] * n[2]. m[10] * n[6]. xz = x*z. float z) { /* m[0] to m[11] are not changed by a translate */ m[12] = m[0] * x + m[4] * y + m[8] * z + m[12]. m[9] * n[2]. ys = y*s. t[0]= m[0] * n[0] + m[4] * n[1] + t[1]= m[1] * n[0] + m[5] * n[1] + t[2]= m[2] * n[0] + m[6] * n[1] + t[3]= m[3] * n[0] + m[7] * n[1] + t[4]= m[0] * n[4] + m[4] * n[5] + t[5]= m[1] * n[4] + m[5] * n[5] + t[6]= m[2] * n[4] + m[6] * n[5] + t[7]= m[3] * n[4] + m[7] * n[5] + m[8]= m[0] * n[8] + m[4] * n[9] + m[9]= m[1] * n[8] + m[5] * n[9] + m[10]=m[2] * n[8] + m[6] * n[9] + m[11]=m[3] * n[8] + m[7] * n[9] + memcpy(m. n[10] = z*z*oneminusc+c. float n[11]. float float float float float float float float float c = (float)cos(rad). n[0] = x*x*oneminusc+c. oneminusc = 1. n[2] = xz*oneminusc-ys. n[5] = y*y*oneminusc+c. float z) { // Normalize axis vector float slength = x*x+y*y+z*z. float y. m[8] * n[10]. float y. m[9] * n[6]. y/=length.} void rotate(float a. x/=length. zs = z*s. } void scale(float x. 189 . s = (float)sin(rad). m[13] = m[1] * x + m[5] * y + m[9] * z + m[13]. n[1] = xy*oneminusc+zs. m[11] * n[2]. // n[7] not used n[8] = xz*oneminusc+ys. m[9] * n[10]. xs = x*s. m[11] * n[10]. m[14] = m[2] * x + m[6] * y + m[10] * z + m[14]. float z) { /* m[12] to m[15] are not changed by a scale */ m[0] *= x. xy = x*y. m[8] * n[6]. float y. } float rad = toRadians(a).8*sizeof(float)). m[15] = m[3] * x + m[7] * y + m[11] * z + m[15].

j++ ) { D[k+n*j] -= beta*D[i+n*j]. m[11] *= z. i < n. j < n2. // A non-singular matrix is one where inv(inv(A)) = A if(!alpha) return false. m[4] *= y. k < n. } // perform the reductions for( i = 0. i++ ) // For each row { alpha = D[i*n+i]. for( k = 0. const int nn = n*n. float alpha. j < n2. m[8] *= z. int i. } // Returns true at success or false if the matrix was singular bool invert(void) { const int n = 4.0. m[6] *= y. i++ ) { for( j = 0. // For each column in this row divide though with the diagonal value so // so alpha becomes 1. float D[n2*n]. j++ ) { D[i+n*j] /= alpha.i<nn. } } } } // Copy result from D to m 190 . i < n.0. int j. m[10] *= z. k++ ) { if( (k-i) != 0 ) { beta = D[k+n*i]. m[7] *= y. for( j = 0.++i) D[i]=m[i]. const int n2 = 2*n. j++ ) { D[i+n*j+nn] = 0. m[9] *= z. // Get diagonal value // Make sure it is not 0. } D[i+n*i+nn] = 1. for (i=0.m[3] *= x. If so the matrix is singular and we will not // invert it. int k. } // Update all other rows. float beta. m[5] *= y. j < n. for( j = i. // init the reduction matrix for( i = 0.

y(v.h" "Matrix. } void transpose() { float temp. Feb 2002. const float y. y(0).x). z(z) {} Vector(const Vector& v) : x(v. m[2] = temp = m[3].z) {} inline Vector& operator+=(const Vector& v) { 191 . float y. Vector. y(y)." << v. // // A generic float vector class. z(0) {} Vector(const float x. m[7] = temp = m[11].sizeof(float)*nn).memcpy(m. m[12] = temp. Vector& v) { os << "(" << v. m[13] = temp. m[4] = temp. m[3] = temp = m[6]. return os. m[1] = temp = m[2].h ////////////////////////////////////////////////////////////////////////////// // Untweaked virtual world (C++ version) // by Jacob Marner. float z.h> class Vector { public: float x. } Vector.h" ostream& operator<<(ostream& os. m[9] = temp.h" <math. m[9]." << v. m[14] = temp. m[12]. z(v. m[11] } }.y << ".x << ". #endif m[4]. m[8].h> <stddef.y). = m[14]. Vector(void) : x(0).&D[nn]. m[13]. Feb 2002. // ////////////////////////////////////////////////////////////////////////////// #include "Vector.z << ")". temp = m[1]. return true. m[6] = temp = m[7]. // ////////////////////////////////////////////////////////////////////////////// #ifndef VECTOR_CLASS #define VECTOR_CLASS #include #include #include #include #include "general. m[8] = temp.h> <ostream. // // A 3d vector float class. const float z) : x(x).cpp ////////////////////////////////////////////////////////////////////////////// // Untweaked virtual world (C++ version) // by Jacob Marner.

y-v.-z). } inline Vector& operator/=(const float c) { x /= c. } inline Vector& operator*=(const float c) { x *= c. } inline Vector operator-(const Vector& v) const { return Vector(x-v. } inline float getLengthSquared(void) const 192 .y.y. } // Copy vector inline Vector& operator=(const Vector& v) { x = v.z*c).x.z.z). return *this. return *this. return *this.z/c). } inline Vector& operator-=(const Vector& v) { x -= v.z).y+v.x += v. y *= c. y = v.x.x. y += v. } inline Vector operator+(const Vector& v) const { return Vector(x+v.-y. y -= v.x. } // Get 2-norm inline float getLength(void) const { return (float)sqrt(x*x+y*y+z*z).y. z = v.z. z -= v. } inline Vector operator*(const float c) const { return Vector(x*c. z *= c.y. } inline Vector operator-(void) const { return Vector(-x.y*c.y/c.y. z /= c. return *this. return *this.z-v.x.z. y /= c. z += v.z+v. } inline Vector operator/(const float c) const { return Vector(x/c.

z-z*v.x-x*v. Each coordinate of lower must be lower // than the same of that of upper.z). inline bool isInRectangle(const Vector& lowerCorner. } inline Vector& normalize(void) { float l = getLength().Abs(y)).-z). } inline void MultLeftMatrix(const { const float* m = ma. 193 . const Vector& upperCorner) const { return (x >= lowerCorner.y). } // Get 2-norm but before the square root is taken inline float getSquaredLength(void) const { return x*x+y*y+z*z.Abs(z)).{ return x*x+y*y+z*z. } inline Vector cross(const Vector& v) const { return Vector(y*v.m. } inline void invert(void) { x = -x. } inline float getInfinityNorm(void) const { return max(max(Abs(x).z*v. z /= l.y. return Vector(x / l. x /= l.y-y*v. + m[10] * z + m[14]. y = -y. } // Return true if this vector is a position between the lower and upper // corner given of a rectangle. z = -z.y && z >= lowerCorner.x*v. return *this. float x1 = m[0] * x + m[4] * y float y1 = m[1] * x + m[5] * y float z1 = m[2] * x + m[6] * y Matrix& ma) + m[8] * z + m[12]. inline Vector normalized(void) const { float l = getLength().-y. y / l. } // Return a normalized version (unit length) of the // vector.z). y /= l.z && x < upperCorner.y && z < upperCorner.x && y < upperCorner.x && y >= lowerCorner. } // Get max of each element (not the same as infinity norm) inline float getMax(void) const { return max(max(x.x). } inline Vector inverted(void) const { return Vector(-x. + m[9] * z + m[13].z. z / l).

} friend ostream& operator<<(ostream&. Just throw w away (it is in fact never // calculated. } inline float dot(const Vector& v) const { return x * v. but do not divide through with w // aftwards. #endif 194 . y = y1. y = y1. x = x1. float x1 = m[0] * x + m[4] * y + m[8] * z + m[12]. y/=w1. x = x1.m. float y1 = m[1] * x + m[5] * y + m[9] * z + m[13]. z/=w1.z. The last row of ma is thus not used.y + z * v. }. z = z1. } } // Do matrix result. z = z1. float z1 = m[2] * x + m[6] * y + m[10] * z + m[14]. inline void MultLeftMatrixIgnoreW(const Matrix& ma) { const float* m = ma. Vector& v).float w1 = m[3] * x + m[7] * y + m[11] * z + m[15].x + y * v. if (w1!=1) { x/=w1.

The main class.java Main. i < args.equals(label)) { return true. A generic 3D vector class of floats.java Vector. public Arguments(String[] args) { this. The class that handles all the call backs.args = args.Part 2: Java source code The source code consists of the following files: Arguments.java BoxData.java EventHandling. Start this to run the application. It stores the 6 planes in world coordinates.java This file is used when parsing command line arguments This class represents the data generated to represent a box. Frustum.java ////////////////////////////////////////////////////////////////////////////// // Untweaked virtual world (GL4Java version) // by Jacob Marner.length. } public boolean getBooleanFlag(String label) { for (int i = 0. This is a BMP loader for gl4Java. Feb 2002.java Matrix. // // This file is used when parsing command line arguments // ////////////////////////////////////////////////////////////////////////////// public class Arguments { String[] args.java gl4java/utils/textures/ BmpTextureLoader. This is similar to the call back functions in GLUT.java Arguments. We also do the scene generation here. This class represents a view frustum.java Node. It has been submitted for inclusion in later versions of GL4Java. A generic 4x4 matrix float class. A node in the scene graph / kd-tree. 195 . i++) { if (args[i].

i < args. try { return Integer. public Vector position = new Vector(). position. public void assign(BoxData b) { scale = b.substring(label.length()).substring(label. i++) { if (args[i]. float defaultvalue) { for (int i = 0. int defaultvalue) { for (int i = 0. } catch (NumberFormatException e) { return defaultvalue. i++) { if (args[i].startsWith(label)) { String s = args[i]. } public float getFloatArgument(String label. } } BoxData.startsWith(label)) { String s = args[i]. i < args.parseInt(s). ////////////////////////////////////////////////////////////////////////////// public class BoxData { public float scale. try { return Float.length. // These are converted to 3D geometry when the kd-tree // (scene graph) is built. // // This class represent the data generated to represent // a box. } catch (NumberFormatException e) { return defaultvalue.parseFloat(s). } } } return defaultvalue. } 196 . } public int getIntegerArgument(String label.length()). } } } return defaultvalue.scale.java ////////////////////////////////////////////////////////////////////////////// // Untweaked virtual world (GL4Java version) // by Jacob Marner.assign(b.assign(b.length. rotation.position). Feb 2002.} } return false. public Vector rotation = new Vector().rotation).

int[] tex = new int[1]. frustumBottom. public final static int WORLD_MAX_Y = 100. } private void stopTimer() { Date endTime = new Date(). double double double double double double frustumLeft.*. // This is similar to the call back functions in // GLUT.}.minValue. Feb 2002. private void startTimer() { starttime = new Date(). gl4java. frustumRight. We also do the scene generation here.0f.out.").util.awt. public float getRandomFloat(float minValue.*. EventHandling.starttime.utils. private GLUFunc glu."). // ////////////////////////////////////////////////////////////////////////////// import import import import import java. frustumFar = 40.drawable. frustumTop.getTime() .java ////////////////////////////////////////////////////////////////////////////// // Untweaked virtual world (GL4Java version) // by Jacob Marner.textures. frustumNear.*. gl4java. class EventHandling implements GLEventListener { public final static int WORLD_MAX_X = 100.*. Node root. float offset = rand.nextFloat() * interval. // Generate a random number between 0 and maxValue. float maxValue) { float interval = maxValue .*. gl4java.out.getTime()) / 1000. Random rand. return minValue + offset. float timeelapsed = (endTime. System. System. // // The class that handles all the call backs. } private GLFunc gl.println("Elapsed time: " + timeelapsed + " sec. public EventHandling() { } Date starttime. public final static int WORLD_MAX_Z = 100. gl4java. } 197 .println( "Average frame rate: " + ((float) numFrames) / timeelapsed + " fps. private GLContext glj.

0. gl.35f). System. GL_TEXTURE_ENV_MODE.readTexture("glow.0f). // Tell OpenGL to use flat shading gl. // Blend lighting a texture effects. 0.glEnable(GL_CULL_FACE). gl. GL_RGB.glCullFace(GL_BACK). GL_DONT_CARE). // Enable fogging gl.getTexture()). // Enable texture mapping gl. texLoader. gl.getGLContext().glPixelStorei(GL_UNPACK_ALIGNMENT. gl. 1).glEnable(GL_TEXTURE_2D). // Turn on z-buffer for hidden surface removal gl.glShadeModel(GL_FLAT).0f. GL_TEXTURE_MIN_FILTER. Node.getImageHeight().glFogi(GL_FOG_MODE.glBindTexture(GL_TEXTURE_2D. GL_LINEAR).glHint(GL_PERSPECTIVE_CORRECTION_HINT.println("Error loading texture.getGLFormat().glEnable(GL_DEPTH_TEST).glGenTextures(1. glu.public void init(GLDrawable drawable) { gl = drawable. gl.glEnable(GL_LIGHTING). gl.glEnable(GL_FOG). 0. gl. glj = drawable."). GL_LINEAR).glTexParameteri(GL_TEXTURE_2D.gluBuild2DMipmaps( GL_TEXTURE_2D.0f. gl. GL_MODULATE).0f. gl.85f)). GL_TEXTURE_MAG_FILTER. GL_NICEST). gl.glFogf(GL_FOG_DENSITY.getGLComponentFormat(). gl. 198 . // This Will Clear The Background Color To Black gl. 0.getGLU(). fogColor). texLoader.glHint(GL_FOG_HINT.0f.0f.glClearColor(0. texLoader. texLoader.err. } //Create Texture gl.0f }. (float) frustumFar). texLoader. // Enable Phong lighting model and one global // directional light.exit(1). gl. GL_LINEAR_MIPMAP_LINEAR).glTexParameteri( GL_TEXTURE_2D. 0.getGL().glEnable(GL_LIGHT0). // Enable trilinear filtering.getImageWidth(). (float) (frustumFar * 0. gl. 0. // Enable back face culling. gl. glu = drawable. glu).bmp"). 0. texLoader.glFogf(GL_FOG_START.glTexEnvf(GL_TEXTURE_ENV. tex[0]). float[] fogColor = { 0. gl.isOk()) { System.0f. if (!texLoader. BmpTextureLoader texLoader = new BmpTextureLoader(gl.gl = gl. gl. tex).glFogf(GL_FOG_END.glFogfv(GL_FOG_COLOR.

0f).0f.z = getRandomFloat(0. pos(3).rotation. i < numBoxes.position. gl. rand = new Random(new Date(). public static int numBoxes.y = getRandomFloat(0.x = getRandomFloat(0. 100.0f). boxData[i]. 0. 199 .glMatrixMode(GL_PROJECTION). 360.0f.5. frustumFar). float angle = (float) frame / 600.0f. for (i = 0.5. frustumTop. WORLD_MAX_Y / 2. int width. frustumBottom = -0.0f).position. } float c = 1.// Generate numBoxes box data put them in array. boxData[i]. Vector cameraPos = new Vector( (float) (WORLD_MAX_X / 2.0f + radius * Math. boxData[i]. BoxData[] boxData = new BoxData[numBoxes].rotation. height). frustumRight.5 * ((double) height / (double) width).0f.glFrustum( frustumLeft.x = getRandomFloat(0.z = getRandomFloat(0. 360.0f.0f. 0. ++i) { boxData[i] = new BoxData(). frustumBottom. 360.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT).5. numBoxes).glLoadIdentity().5 * ((double) height / (double) width). frustumLeft = -0.rotation. boxData[i]. (float) (WORLD_MAX_Z / 2. width. startTimer(). boxData[i]. boxData[i]. 1. gl.1f.getTime()).5f). // Move camera in circle around center of world. } root = new Node(boxData. frustumRight = 0. For each one // store scale(1).0f + radius * Math. int frame = 0. rotation(3).0f.position. } public void reshape(GLDrawable d. float radius = 20. public void display(GLDrawable drawable) { //Clear The Screen And The Depth Buffer gl.0f. public static int numFrames. WORLD_MAX_Y). frustumTop = 0.0f.y = getRandomFloat(0. WORLD_MAX_Z). int i.cos(angle)). frustumNear. boxData[i]. gl.glMatrixMode(GL_MODELVIEW). frustumNear = 0. reshape(drawable.glLoadIdentity().scale = getRandomFloat(0. WORLD_MAX_X).glViewport(0. gl. 100). Generate each number // randomly.sin(angle))). //Reset The View gl. int height) { gl.

0f }.cos(angle + 3. gl. root. float[] mat_shininess = { 50. gl. gl.z + cameraDirection. GL_SPECULAR.exit(0).render(frustum). (float) frustumBottom.0f. 1.0f.0f. glu. GL_POSITION. (float) (Math. 1. 1.0f)).Vector cameraDirection = new Vector( (float) (Math.0f.glMaterialfv(GL_FRONT.glBindTexture(GL_TEXTURE_2D. cameraPos.y + cameraDirection.gluLookAt( cameraPos. gl. GL_DIFFUSE. (float) frustumLeft.0f.0f }.0f.z).z. 0. upVector. gl. 1. upVector.0f))).x. if (frame >= numFrames) { stopTimer(). float[] specular_light = { 0. 1.glEnd(). diffuse_light). 1. 1.0f }. 0. upVector. float[] ambient_light = { 1.0f.0f }.14f / 2. Vector upVector = new Vector(0. (float) frustumNear. 0. specular_light). 1. (float) frustumFar). gl.glLightfv(GL_LIGHT0. 0). GL_SPECULAR. gl. ambient_light). light_position). cameraPos. mat_specular).0f.0f.0f }. cameraPos. cameraPos.glLightfv(GL_LIGHT0. (float) frustumRight.y. tex[0]). gl.x + cameraDirection.glLightfv(GL_LIGHT0. 1. System. GL_SHININESS. 1.0f. cameraDirection.x.14f / 2.0f }.glBegin(GL_QUADS). 0. gl.0f. float[] light_position = { 0. (float) frustumTop. GL_AMBIENT. frame++.0f.x. float[] mat_specular = { 1. 1.glMaterialfv(GL_FRONT.0f.0f.glLightfv(GL_LIGHT0. upVector.y. 0. } } public void cleanup(GLDrawable drawable) { } public void preDisplay(GLDrawable drawable) { } public void postDisplay(GLDrawable drawable) { } } 200 . cameraPos.0f.sin(angle + 3.y. float[] diffuse_light = { 1.z. mat_shininess). -1.0f. Frustum frustum = new Frustum( cameraPos.

points[5] = cameraPos. i++) points[i] = cameraPos. for (i = 0.subtract(cameraPos)). normals[0] = (leftNearPoint.negate(). points[4] = centerOfNearPlane. cameraUp. // Calculate normal for left side Vector leftNearPoint = cameraDirection. 2. It stores the // 6 planes in world coordinates.multiply(pfar)).java ////////////////////////////////////////////////////////////////////////////// // Untweaked virtual world (GL4Java version) // by Jacob Marner. // The first three args is information about the camera in the world. // The camera is in the plane of the sides. cameraDirection. The general scheme is: 1.cross(cameraDirection)).bottom. // The last 6 is the parameters given to glFrustum(). float top. cameraUp = cameraDirection. // ////////////////////////////////////////////////////////////////////////////// class Frustum { // points and normals for planes in this order: // left.negate(). 3.normalize(). It is now paralell to the near plane. float left. Feb 2002. We have one point on the plane as the cameraPos. int i. Vector cameraUp. By moving along the near plane with the given amount we get another point in the plane.near. // Normal points towards camera normals[5] = cameraDirection.Frustum. Vector centerOfNearPlane = cameraPos.normalize(). float pnear.cross(cameraUp.top. Frustum( Vector cameraPos. // Points for the near anf far planes are found by intersection // viewing direction line with plane. // Normal points away from camera // // // // // // // Calculate normals for sides. float right.multiply(pnear)). // Make sure that the up vector is perpendular to the // cameraDirection.right) or the perpendular of the upvector (top. // // This class represents a view frustum. i < 4. Vector cameraDirection.right.far. public Vector[] normals = new Vector[6].add(cameraDirection. float radiusSquared.cross(cameraUp). float bottom.add(centerOfNearPlane). float pfar) { // Make sure that all the direction vectors are unit vectors.add(cameraDirection. normals[4] = cameraDirection.cross(cameraUp). crossed with the upvector (left. public Vector center. These two points can. 201 . public Vector[] points = new Vector[6].bottom) give us the normal of the plane.multiply(left). // Set normals for near and far planes.

Vector extremeNearCorner = centerOfNearPlane. Vector extremeFarCorner = extremeNearCorner. import gl4java. // Calculate normal for top side Vector topNearPoint = centerOfNearPlane. // ////////////////////////////////////////////////////////////////////////////// import java.add( cameraPos).multiply(right).subtract(center). normals[2] = cameraDirection .cross(cameraUp).add(cameraUp.*. Default: 1000.max(right. Feb 2002.add(centerOfNearPlane).subtract(points[4]). -left))).multiply(Math. import gl4java.getSquaredLength().add(points[4]).*. // // The application takes two optional parameters: // -frames:<int> : The number of frames to run the animation.swing.awt.awt.awt. // Calculate normal for bottom side Vector bottomNearPoint = centerOfNearPlane.max(top.cross(cameraUp). } } Main. public class Main extends JFrame { //Our rendering canvas //We are using GLAnimCanvas because we want the canvas //to be constantly redrawn GLAnimCanvas canvas = null.*.add(cameraUp.cross(cameraUp).*. import javax.subtract(cameraPos)).cross(bottomNearPoint. import java.multiply(bottom)).divide(pnear). Start this to run the application.event.subtract(cameraPos)). normals[3] = cameraDirection.// Calculate normal for right side Vector rightNearPoint = cameraDirection.cross(cameraUp) . // Calculate the center of the frustum center = points[5].*.add(cameraUp.cross(topNearPoint. -bottom))). normals[1] = (rightNearPoint.subtract(cameraPos)) . // // The main class.cross(cameraUp).*. radiusSquared = extremeFarCorner. import gl4java.multiply(top)). // -boxes:<int> : The number of boxes to generate.negate(). public Main(String s) { super(s). } public static void main(String args[]) { 202 . Default: 3000.divide(2).multiply(pfar).drawable.subtract(cameraPos).java ////////////////////////////////////////////////////////////////////////////// // Untweaked virtual world (GL4Java version) // by Jacob Marner.add( cameraDirection.multiply(Math.

getSize(). // Set the size of the Window Insets i = f.setUseRepaint(false). // Create and add the class with the callback methods EventHandling demo = new EventHandling().getContentPane().canvas = GLDrawableFactory.height). f.start(). canvas. canvas.numBoxes = arg.java ////////////////////////////////////////////////////////////////////////////// // Untweaked virtual world (GL4Java version) // by Jacob Marner. d.gljThreadDebug = false.setVisible(true).numFrames = arg.requestFocus().top).getInsets(). f. } public void windowClosing(WindowEvent e) { windowClosed(e).addWindowListener(new WindowAdapter() { public void windowClosed(WindowEvent e) { System. 0. } } Matrix.gljNativeDebug = false.otherwise the insets are not // initialized. // // A generic 4x4 matrix float class.getContentPane().createGLAnimCanvas(caps. } }). EventHandling. GLAnimCanvas canvas = f.getIntegerArgument("-boxes:". f. canvas.setUseFpsSleep(false).width. canvas). f. // Create the 3D canvas Dimension d = f. canvas. // // The internal matrix representation is directly // compatible with that of OpenGL.getIntegerArgument("-frames:". canvas. // Turn of vertical sync and any other pauses. d. so you can use // glLoadMatrix() on m to import the matrix into 203 .add("Center". 640 + i.left.setUseYield(false). EventHandling.addGLEventListener(demo).bottom + i.Main f = new Main("Virtual World"). 480 + i. GLContext. GLCapabilities caps = new GLCapabilities(). GLContext. f. GLContext.setLayout(new BorderLayout()).setBounds(0.getFactory() .gljClassDebug = false. Feb 2002.right + i. // We pack the frame .pack(). Arguments arg = new Arguments(args).exit(0). canvas. 3000). f. 1000). // // All operations are applied such that the // Matrix itself is the Left operand.

m[1] = m[2] = m[3] = m[4] = m[6] = m[7] = m[8] = m[9] = m[11] = m[12] = m[13] = m[14] = 0. t[9] = m[1] * n[8] + m[5] * n[9] + m[9] * n[10] + m[13] * n[11]. m[15] = m15. float m8. t[6] = m[2] * n[4] + m[6] * n[5] + m[10] * n[6] + m[14] * n[7]. float m14.m[i]. } public Matrix(float m0. float m1. float m5. t[11] = m[3] * n[8] + m[7] * n[9] + m[11] * n[10] + m[15] * n[11]. public Matrix() { } public void loadIdentity() { m[0] = m[5] = m[10] = m[15] = 1. m[12] = m12. m[1] = m1. float m7. i++) m[i] = ma. } public void assign(Matrix ma) { for (int i = 0. float m4. float m10. t[2] = m[2] * n[0] + m[6] * n[1] + m[10] * n[2] + m[14] * n[3]. m[4] = m4.m[i]. float[] t = new float[12]. m[9] = m9. float m9. i++) m[i] = ma. m[13] = m[1] * n[12] + m[5] * n[13] + m[9] * n[14] + m[13] * n[15]. t[0] = m[0] * n[0] + m[4] * n[1] + m[8] * n[2] + m[12] * n[3]. t[8] = m[0] * n[8] + m[4] * n[9] + m[8] * n[10] + m[12] * n[11]. m[3] = m3.// OpenGL. m[7] = m7. t[4] = m[0] * n[4] + m[4] * n[5] + m[8] * n[6] + m[12] * n[7]. } public Matrix(Matrix ma) { for (int i = 0. float m6. i < 16. m[8] = m8. m[13] = m13. m[5] = m5. t[3] = m[3] * n[0] + m[7] * n[1] + m[11] * n[2] + m[15] * n[3]. t[1] = m[1] * n[0] + m[5] * n[1] + m[9] * n[2] + m[13] * n[3].m. float m3. float m13. m[10] = m10. m[6] = m6. float m11. // ////////////////////////////////////////////////////////////////////////////// class Matrix { public float[] m = new float[16]. float m2. t[5] = m[1] * n[4] + m[5] * n[5] + m[9] * n[6] + m[13] * n[7]. float m15) { m[0] = m0. t[10] = m[2] * n[8] + m[6] * n[9] + m[10] * n[10] + m[14] * n[11]. m[11] = m11. } public void mult(Matrix ma) { float[] n = ma. i < 16. t[7] = m[3] * n[4] + m[7] * n[5] + m[11] * n[6] + m[15] * n[7]. 204 . m[14] = m14. float m12. m[2] = m2. m[12] = m[0] * n[12] + m[4] * n[13] + m[8] * n[14] + m[12] * n[15].

float z) { // Normalize axis vector float slength = x * x + y * y + z * z.cos(rad). i < 12. n[9] = yz * oneminusc . s = (float) Math. float[] n = new float[11]. } public void rotate(float a. m[10] = m[2] * n[8] + m[6] * n[9] + m[10] * n[10]. if (slength != 1. } public void translate(float x. n[2] = xz * oneminusc . float y.ys. m[13] = m[1] * x + m[5] * y + m[9] * z + m[13]. t[5] = m[1] * n[4] + m[5] * n[5] + m[9] * n[6]. // n[7] not used n[8] = xz * oneminusc + ys. i < 8. float float float float float float float float float c = (float) Math. t[2] = m[2] * n[0] + m[6] * n[1] + m[10] * n[2]. x /= length. float z) { /* m[0] to m[11] are not changed by a translate */ m[12] = m[0] * x + m[4] * y + m[8] * z + m[12]. n[0] = x * x * oneminusc + c. t[7] = m[3] * n[4] + m[7] * n[5] + m[11] * n[6]. m[15] = m[3] * x + m[7] * y + m[11] * z + m[15]. xz = x * z. t[4] = m[0] * n[4] + m[4] * n[5] + m[8] * n[6]. m[8] = m[0] * n[8] + m[4] * n[9] + m[8] * n[10]. for (int i = 0.toRadians(a). xy = x * y.0f .0f) { float length = (float) Math. ys = y * s. n[5] = y * y * oneminusc + c. float y. n[10] = z * z * oneminusc + c. z /= length.sqrt(slength). i++) m[i] = t[i]. t[0] = m[0] * n[0] + m[4] * n[1] + m[8] * n[2]. xs = x * s. m[15] = m[3] * n[12] + m[7] * n[13] + m[11] * n[14] + m[15] * n[15]. m[14] = m[2] * x + m[6] * y + m[10] * z + m[14]. } float rad = (float) Math. // n[3] not used n[4] = xy * oneminusc . t[3] = m[3] * n[0] + m[7] * n[1] + m[11] * n[2]. m[11] = m[3] * n[8] + m[7] * n[9] + m[11] * n[10]. zs = z * s. m[9] = m[1] * n[8] + m[5] * n[9] + m[9] * n[10]. float y. float z) 205 . t[1] = m[1] * n[0] + m[5] * n[1] + m[9] * n[2]. t[6] = m[2] * n[4] + m[6] * n[5] + m[10] * n[6]. } public void scale(float x. float x. /* m[12] to m[15] are not changed by a rotate */ float[] t = new float[8]. y /= length.sin(rad).zs.c. yz = y * z.xs. n[6] = yz * oneminusc + xs.m[14] = m[2] * n[12] + m[6] * n[13] + m[10] * n[14] + m[14] * n[15]. oneminusc = 1. n[1] = xy * oneminusc + zs. for (int i = 0. i++) m[i] = t[i].

// Update all other rows. } 206 .0f. int k. float beta. m[4] *= y. for (k = 0. for (i = 0. float alpha. k < n. int j. m[8] *= z. If so the matrix is singular and we will not // invert it. i++) // For each row { alpha = D[i * n + i]. m[3] *= x. j++) { D[k + n * j] -= beta * D[i + n * j]. i < n. final int n2 = 2 * n. } // perform the reductions for (i = 0. // A non-singular matrix is one where inv(inv(A)) = A if (alpha == 0. m[10] *= z. j < n2. // Get diagonal value // Make sure it is not 0. // init the reduction matrix for (i = 0. m[1] *= x. j++) { D[i + n * j + nn] = 0.{ /* m[12] to m[15] are not changed by a scale */ m[0] *= x. final int nn = n * n. m[7] *= y. { D[i + n * } column in this row divide though with the diagonal value so becomes 1. i++) { for (j = 0. j < n. ++i) D[i] = m[i]. // For each // so alpha for (j = 0. float[] D = new float[n2 * n]. j++) j] /= alpha. m[11] *= z. for (j = i. m[9] *= z.0f.i) != 0) { beta = D[k + n * i]. } D[i + n * i + nn] = 1. i < n. j < n2. } // Returns true at success or false if the matrix was singular public boolean invert() { final int n = 4. i < nn. int i. m[5] *= y. m[2] *= x. m[6] *= y. k++) { if ((k .0f) return false.

// Find and set bounding box by iterating through boxes. static final int MAX_BOXES_PER_LEAF = 10.util. ++c) { float boxMaxDistFromCenter = (float) Math. temp = m[6]. 2)).*. public Node(BoxData boxData[]. m[3] = m[12]. 207 . class Node implements GLEnum { public static GLFunc gl. 0). m[6] = m[9]. m[9] = temp.2)) ) for (int c = startindex. // // Every node has a bounding box and zero or two // children. // ////////////////////////////////////////////////////////////////////////////// import import import import java.x .java ////////////////////////////////////////////////////////////////////////////// // Untweaked virtual world (GL4Java version) // by Jacob Marner.y .sqrt(2.x = candidate. candidate = boxData[c]. 0. temp = m[3]. m[1] = m[4]. Each node contains a display list. Node. temp = m[1]. i++) m[i] = D[i + nn].GLContext. i < nn.pow(boxData[c]. c < startindex + numindex. } public void transpose() { float temp.0f * (float) Math. temp = m[7]. gl4java. n is the size of the array. m[4] = temp.*. m[7] = m[13]. if (candidate < boundingBox[0]. EventHandling.} } } // Copy result from D to m for (i = 0.position. temp = m[2]. m[12] = temp. m[8] = temp. int startindex. gl4java. gl4java. float candidate. temp = m[11].boxMaxDistFromCenter.*. // This method may reorder the ordering of the // box data array. EventHandling.x) boundingBox[0]. candidate = boxData[c].position. // (Boxes is centered about position and has a distance // from center of max sqrt(2*pow(scale. Feb 2002.awt. // // A node in the scene graph / kd-tree.WORLD_MAX_X. m[13] = temp. m[11] = m[14]. boundingBox[1] = new Vector(0.boxMaxDistFromCenter. m[2] = m[8]. return true.scale. m[14] = temp. int numindex) { boundingBox[0] = new Vector(EventHandling.WORLD_MAX_Z).WORLD_MAX_Y. } }.

if (candidate < boundingBox[0].y) boundingBox[0].y = candidate; candidate = boxData[c].position.z - boxMaxDistFromCenter; if (candidate < boundingBox[0].z) boundingBox[0].z = candidate; candidate = boxData[c].position.x + boxMaxDistFromCenter; if (candidate > boundingBox[1].x) boundingBox[1].x = candidate; candidate = boxData[c].position.y + boxMaxDistFromCenter; if (candidate > boundingBox[1].y) boundingBox[1].y = candidate; candidate = boxData[c].position.z + boxMaxDistFromCenter; if (candidate > boundingBox[1].z) boundingBox[1].z = candidate; } boxCenter = boundingBox[1].subtract(boundingBox[0]).divide(2).add(boundingBox[0]); boxRadiusSquared = boxCenter.subtract(boundingBox[0]).getSquaredLength(); // If n is more than max_boxes if (numindex > MAX_BOXES_PER_LEAF) { float xspan = boundingBox[1].x - boundingBox[0].x; float yspan = boundingBox[1].y - boundingBox[0].y; float zspan = boundingBox[1].z - boundingBox[0].z; int splitn; if (xspan >= yspan && xspan >= zspan) { // Find center along max axis float center = boundingBox[0].x + xspan / 2.0f; // Reorder box data according to center on the max axis. splitn = reorder(boxData, startindex, numindex, center, 0); } else { if (yspan >= zspan) { // Find center along max axis float center = boundingBox[0].y + yspan / 2.0f; // Reorder box data according to center on the max axis. splitn = reorder(boxData, startindex, numindex, center, 1); } else { // Find center along max axis float center = boundingBox[0].z + zspan / 2.0f; // Reorder box data according to center on the max axis. splitn = reorder(boxData, startindex, numindex, center, 2); } } // Create nodes for each child leftChild = new Node(boxData, startindex, splitn - startindex); rightChild = new Node(boxData, splitn, startindex + numindex - splitn); // Initialize display list that calls children display list displayList = gl.glGenLists(1); if (displayList == 0) { System.err.println("Internal error. Display used before set.");

208

System.exit(1); } gl.glNewList(displayList, GL_COMPILE); gl.glCallList(leftChild.displayList); gl.glCallList(rightChild.displayList); gl.glEndList(); } else { // Initialize display list to draw the boxes. This display list // must be executed within a begin() end() block that was started // with GL_QUAD displayList = gl.glGenLists(1); if (displayList == 0) { System.err.println("Internal error. Could not allocate display list."); System.exit(1); } gl.glNewList(displayList, GL_COMPILE); for (int i = startindex; i < startindex { Matrix m = new Matrix(); m.loadIdentity(); m.translate( boxData[i].position.x, boxData[i].position.y, boxData[i].position.z); m.rotate(boxData[i].rotation.x, 1, 0, m.rotate(boxData[i].rotation.y, 0, 1, m.rotate(boxData[i].rotation.z, 0, 0, float scale = boxData[i].scale; m.scale(scale, scale, scale);

+ numindex; ++i)

0); 0); 1);

Matrix t = new Matrix(m); if (!t.invert()) { System.err.println("Internal error. Matrix invert failed."); System.exit(1); } t.transpose(); // Left side transformAndSetNormal(t, -1, transformAndDrawVertex(m, 1, transformAndDrawVertex(m, 1, transformAndDrawVertex(m, 0, transformAndDrawVertex(m, 0,

0, 0, 1, 1, 0,

0); -1, -1, -1, -1,

-1, 1); 1, 1); 1, -1); -1, -1);

// Front side transformAndSetNormal(t, 0, 0, 1); transformAndDrawVertex(m, 1, 0, 1, -1, 1); transformAndDrawVertex(m, 1, 1, 1, 1, 1); transformAndDrawVertex(m, 0, 1, -1, 1, 1); transformAndDrawVertex(m, 0, 0, -1, -1, 1); // Right side transformAndSetNormal(t, 1, 0, 0); transformAndDrawVertex(m, 1, 0, 1, transformAndDrawVertex(m, 1, 1, 1, transformAndDrawVertex(m, 0, 1, 1, transformAndDrawVertex(m, 0, 0, 1,

-1, -1); 1, -1); 1, 1); -1, 1);

// Top side transformAndSetNormal(t, 0, 1, 0); transformAndDrawVertex(m, 1, 0, 1, 1, 1); transformAndDrawVertex(m, 1, 1, 1, 1, -1); transformAndDrawVertex(m, 0, 1, -1, 1, -1);

209

transformAndDrawVertex(m, 0, 0, -1, 1, 1); // Back side transformAndSetNormal(t, 0, 0, -1); transformAndDrawVertex(m, 1, 0, -1, -1, -1); transformAndDrawVertex(m, 1, 1, -1, 1, -1); transformAndDrawVertex(m, 0, 1, 1, 1, -1); transformAndDrawVertex(m, 0, 0, 1, -1, -1); // Under side transformAndSetNormal(t, 0, -1, transformAndDrawVertex(m, 1, 0, transformAndDrawVertex(m, 1, 1, transformAndDrawVertex(m, 0, 1, transformAndDrawVertex(m, 0, 0, } gl.glEndList(); } } protected void finalize() { gl.glDeleteLists(displayList, 1); } // First min box then max box Vector[] boundingBox = new Vector[2]; boolean isInside; // We do a frustum culling algorithm, consisting of // 1. Bounding spheres for the frustum and bounding box // is precalculated. These are used to do a bounding sphere // culling. // 2. Plane to point comparison to see of the box // is entirely outside the plane. We only test to points // per bounding box for each plane. // 3. Temporal coherance culling. We remember what plane // was used to cull this the last time and we start with // that. // Note: We do not use the octant test to reduce the plane // comparisons needed to 3 since that requires a symmetric // frustum. public void render(Frustum frustum) { isInside = true; int plane; if ((boxCenter.subtract(frustum.center)).getSquaredLength() > frustum.radiusSquared + boxRadiusSquared) return; if (basicCullTest(lastPlaneCulled, frustum)) return; for (plane = 0; plane < 6; plane++) { if (plane == lastPlaneCulled) continue; if (basicCullTest(plane, frustum)) { lastPlaneCulled = plane; return; } } // Is the box fully within the frustum? If yes, simply render the // display list. if (isInside)

0); 1, -1, -1); 1, -1, 1); -1, -1, 1); -1, -1, -1);

210

{ gl.glCallList(displayList); return; } // We are now in doubt whether the box should be drawn. // If it is a leaf draw it all, if not go to children nodes. if (leftChild == null) { gl.glCallList(displayList); } else { leftChild.render(frustum); rightChild.render(frustum); } } int displayList; Node leftChild = null; Node rightChild = null; int lastPlaneCulled = 0; private void transformAndDrawVertex( Matrix m, float texx, float texy, float posx, float posy, float posz) { gl.glTexCoord2f(texx, texy); Vector w = new Vector(posx, posy, posz); w.MultLeftMatrix(m); gl.glVertex3f(w.x, w.y, w.z); } private void transformAndSetNormal(Matrix t, float x, float y, float z) { Vector v = new Vector(x, y, z); v.MultLeftMatrixIgnoreW(t); v.normalize(); gl.glNormal3f(v.x, v.y, v.z); } static private boolean isOutside( float x, float y, float z, Vector point, Vector normal) { return normal.dot(new Vector(x, y, z).subtract(point)) >= 0; } private boolean basicCullTest(int plane, Frustum frustum) { // Find corner furthest from plane in normals direction. int nx = frustum.normals[plane].x >= 0 ? 1 : 0; int ny = frustum.normals[plane].y >= 0 ? 1 : 0; int nz = frustum.normals[plane].z >= 0 ? 1 : 0; // Check if corner furthest from plane in normals direction is outside. // If not it is fully inside. if (isOutside(boundingBox[nx].x, boundingBox[ny].y, boundingBox[nz].z, frustum.points[plane], frustum.normals[plane]))

211

{ // Check if corner closest to plane in normal's direction is outside. // If yes, the box is fully outside. // If no, the box is intersecting the fustrum border. if (isOutside(boundingBox[1 - nx].x, boundingBox[1 - ny].y, boundingBox[1 - nz].z, frustum.points[plane], frustum.normals[plane])) { return true; } else isInside = false; } return false; } Vector boxCenter; float boxRadiusSquared; // Reorder boxData array according to value at given offset. If offset is 0 then // x is used. 1 gives y. 2 gives z. static int reorder( BoxData boxData[], int startindex, int numindex, float splitvalue, int offset) { BoxData temp = new BoxData(); if (numindex < 1) return 0; int r = startindex + numindex - 1; int p = startindex; int i = p - 1; int j = r + 1; while (true) { i++; j--; while (j >= p && boxData[j].position.index(offset) > splitvalue) j--; while (i <= r && boxData[i].position.index(offset) < splitvalue) i++; if (i < j) { temp.assign(boxData[i]); boxData[i].assign(boxData[j]); boxData[j].assign(temp); } else { while (j < r && boxData[j].position.index(offset) < splitvalue) j++; if (j <= p) j = p; return j; } } } };

212

Vector.java
////////////////////////////////////////////////////////////////////////////// // Untweaked virtual world (GL4Java version) // by Jacob Marner. Feb 2002. // // A generic 3D vector class of floats. // Note that objects of this class are mutable. // ////////////////////////////////////////////////////////////////////////////// final public class Vector { public float x; public float y; public float z; public Vector() { x = 0; y = 0; z = 0; } public Vector(float x, float y, float z) { this.x = x; this.y = y; this.z = z; } public Vector(Vector v) { x = v.x; y = v.y; z = v.z; } public Vector add(Vector v) { return new Vector(x + v.x, y + v.y, z + v.z); } public Vector subtract(Vector v) { return new Vector(x - v.x, y - v.y, z - v.z); } public Vector divide(float c) { return new Vector(x / c, y / c, z / c); } public Vector negate() { return new Vector(-x, -y, -z); } public void negated() { x = -x; y = -y; z = -z; } public Vector multiply(float c) { return new Vector(x * c, y * c, z * c); } public void added(Vector v)

213

y /= l. z = v.abs(y)).max(x.y.x. // Copy vector public void assign(Vector v) { x = v. } public void normalize() { float l = getLength(). Math. y += v.y. y / l.max(Math.{ x += v. void divided(float c) c. 214 .x. z += v. } // Return a normalized version (unit length) of the // vector.x.max(Math. v. c. c. } // Get 2-norm public float getLength() { return (float) Math. x /= l. } // Get 2-norm but before the square root is taken public float getSquaredLength() { return x * x + y * y + z * z. Math. } // Get max of each element (not the same as infinity norm) public float getMax() { return Math. public Vector normalized() { float l = getLength().z. z / l).sqrt(x * x + y * y + z * z). return new Vector(x / l. } public { x -= y -= z -= } public { x *= y *= z *= } public { x /= y /= z /= } void subtracted(Vector v) v.abs(x).abs(z)). y = v. y). c. void multiplied(float c) c. c.z.max(Math.y.z. z). } public float getInfinityNorm() { return Math. v.

-y.x && y < upperCorner. The last row of ma is thus not used.y && z < upperCorner.x && y >= lowerCorner. case 2 : return z.m. Vector upperCorner) { return ( x >= lowerCorner.z. y on 1 and z on 2.z). x * v.y && z >= lowerCorner. float x1 = m[0] * x + m[4] * y + m[8] * z + m[12].m. float z1 = m[2] * x + m[6] * y + m[10] * z + m[14]. public void MultLeftMatrixIgnoreW(Matrix ma) { float[] m = ma. } } // Do matrix result. float y1 = m[1] * x + m[5] * y + m[9] * z + m[13]. } public Vector inverted() { return new Vector(-x.x). public boolean isInRectangle(Vector lowerCorner. y = y1.x * v. z = z1. } // Return true if this vector is a position between the lower and upper // corner given of a rectangle. Just throw w away (it is in fact never // calculated. y = -y.y. case 1 : return y. } public void MultLeftMatrix(Matrix ma) { float[] m = ma. } // Return x on 0. } public Vector cross(Vector v) { return new Vector(y * v. 215 . float z1 = m[2] * x + m[6] * y + m[10] * z + m[14]. z = -z.z * v. Each coordinate of lower must be lower // than the same of that of upper. default : throw new ArrayIndexOutOfBoundsException(). z * v.z . float x1 = m[0] * x + m[4] * y + m[8] * z + m[12]. -z).z && x < upperCorner. public float index(int n) { switch (n) { case 0 : return x.y * v. } public void invert() { x = -x.z /= l.x .y . x = x1. but do not divide through with w // afterwards. float w1 = m[3] * x + m[7] * y + m[11] * z + m[15]. float y1 = m[1] * x + m[5] * y + m[9] * z + m[13].

2 but I have // submitted it for inclusion in later versions so // other people might benefit from it.image.awt. // // This is a BMP loader for gl4Java.event. java.java ////////////////////////////////////////////////////////////////////////////// // Untweaked virtual world (GL4Java version) // by Jacob Marner.*. 8-bit and 24-bit colors. /** * This class implements a Window .8.y + z * v.*. * * The Windows BMP format does not support * multiple layers or transparency.*. * The rest of the PixelStore * arguments should stay at the default. import import import import import import import java. Feb 2002.Color. z = z1.*. GLUFunc glu) 216 . 1). y /= w1. java. // // It was not included with gl4Java 2.applet. java.awt. This loader implements the * full . } }. java. * * Before using the loaded texture data you * should call * glPixelStorei(GL_UNPACK_ALIGNMENT. * * @author Jacob Marner jacob@marner.utils. It supports * 1-bit. java. } } public float dot(Vector v) { return x * v.*.x + y * v.BMP images according to the // specification into textures.*. // // It loads Windows .awt. // ////////////////////////////////////////////////////////////////////////////// package gl4java.BMP * texture loader.net. import gl4java. z /= w1. 4-bit.BMP specification. if (w1 != 1) { x /= w1.dk * @see IOTextureLoader * @see TextureLoader */ public class BmpTextureLoader extends IOTextureLoader { public BmpTextureLoader(GLFunc gl.x = x1.*.textures.awt. y = y1.*.io. java. * The 4-bit and 8-bit mode can be RLE encoded.z. gl4java/utils/textures/BmpTextureLoader. * This loader supports all the variants.

If it is 1. You proably used a internal // format not accepted by your OpenGL implementation or you may have run // out of available texture names. private static int compression. setTextureSize(). // The file could not be found or it could not be opened for reading. pixel = bitmapData[0]. 4. glu). // The offset from the BITMAPFILEHEADER structure // to the actual bitmap data in the file. private InputStream file. // Compression private final static int BI_RGB = 0. private final static int BI_RLE4 = 2. 217 . private final static int OUT_OF_MEMORY = 4. if (res != SUCCESS) { error = true. private int bitCount. // Image height in pixels private int height. imageHeight = height. Is 1. private int byteOffset. 24. // Image width in pixels private int width. private final static int BI_RLE8 = 1. 8. // The file does not comply with the specification. // The system ran out of heap memory during the texture load. private final static int COULD_NOT_FIND_OR_READ_FILE = 1.4 or 8 then // images is paletted. othewise not. private byte[][] palette = new byte[3][256]. } imageWidth = width. // The number of bytes read so far private int bytesRead.{ super(gl. // Number of bit per pixel. private final static int OPENGL_ERROR = 3. int res = loadBMP(is. bitmapData). glFormat = GL_RGB. return false. // OpenGL could not accept the texture. } // The operation ended successfully private final static int SUCCESS = 0. } protected boolean readTexture(InputStream is) { byte[][] bitmapData = new byte[1][]. return true. // Palette used for paletted images during load. private final static int ILLEGAL_FILE_FORMAT = 2.

} } // Reads and returns a 8-bit value from the file (0-255) private int read8BitValue() { bytesRead += 1. // DWORD bfOffBits. file. // UINT bfReserved1. private int readFileHeader() { // Read "BM" tag in ASCII.read().read(). // UINT bfReserved2.read(). // DWORD bfSize. } } // In Windows terms read this structure // typedef struct tagBITMAPFILEHEADER { /* bmfh */ // UINT bfType. } catch (IOException e) { return 0. try { int c1 = file. if (read8BitValue() != 66 || read8BitValue() != 77) return ILLEGAL_FILE_FORMAT.read(). return c1 + (c2 << 8) + (c3 << 16) + (c4 << 24). try { int int int int c1 c2 c3 c4 = = = = file. 218 . } catch (IOException e) { return 0. } } // Reads and returns a 16-bit value from the file private short read16BitValue() { bytesRead += 2.read(). try { int c1 = file.// Reads and returns a 32-bit value from the file. } catch (IOException e) { return 0. private int read32BitValue() { bytesRead += 4. // } BITMAPFILEHEADER. return c1. read32BitValue().read(). return (short) (c1 + (c2 << 8)). file. file. // Read file size.read(). We do not need this. int c2 = file. Ignore.

read32BitValue(). // LONG biWidth. if (bitCount != 1 && bitCount != 4 && bitCount != 8 && bitCount != 24) return ILLEGAL_FILE_FORMAT. sizeOfInfoHeader -= 40. read16BitValue(). // Apply padding in end of header to be forward compatible. This is irrelevant since we simply // want the bitmap. } // In windows terms read this struct: //typedef struct tagBITMAPINFOHEADER { /* bmih */ // DWORD biSize. //} BITMAPINFOHEADER. private int readInfoHeader() { // We expect this to be at least 40. // Read the number of important colors. read32BitValue(). read32BitValue(). return SUCCESS. // Pixel to device mapping. // Read the byte offset byteOffset = read32BitValue(). // DWORD biClrUsed. if (read16BitValue() != 1) return ILLEGAL_FILE_FORMAT. This will not be needed // in OpenGL so we will ignore this. // LONG biHeight.// Skip the two reserved areas read16BitValue(). if (compression != BI_RGB && compression != BI_RLE8 && compression != BI_RLE4) return ILLEGAL_FILE_FORMAT. bitCount = read16BitValue(). if (sizeOfInfoHeader < 40) return ILLEGAL_FILE_FORMAT. // LONG biXPelsPerMeter. // Read number of planes. // LONG biYPelsPerMeter. 219 . // DWORD biCompression. // WORD biPlanes. height = read32BitValue(). int sizeOfInfoHeader = (int) read32BitValue(). // Read image size. If it is larger we read // some more bytes in the end to be forward compatible. We do not need this since we have the // image size. // Read colors used. // DWORD biClrImportant. // WORD biBitCount. read32BitValue(). read32BitValue(). compression = read32BitValue(). According to the specification this // must be 1. so it is ignored. We do not need this. width = read32BitValue(). // DWORD biSizeImage.

sizeOfInfoHeader--. bitmapData[index + x * 3 + 2] = palette[2][color]. } return SUCCESS. int numColors = 1 for (int i = 0. y++) { int index = y * width * 3. } return SUCCESS. } return SUCCESS. while (bytesRead % 4 != 0) read8BitValue(). // Skip reversed byte. private int readPalette() { // 24-bit images are not paletted. int byteRead = 0. i { // Read RGB. } // The palette follows directly after the // info header // //typedef struct tagRGBQUAD { /* rgbq */ // BYTE rgbBlue. // BYTE rgbRed. // } RGBQUAD. // BYTE rgbReserved. if (bitCount == 24) return SUCCESS. } // This is called after the first byte has been found to be 0 220 . for (int x = 0.((char) (x % 8)))) & 1). j--) = (byte) read8BitValue(). y < height. bitmapData[index + x * 3] = palette[0][color]. x < width. i++) j >= 0. } private int readBitmapData1Bit(byte[] bitmapData) { // 1-bit format cannot be compressed if (compression != BI_RGB) return ILLEGAL_FILE_FORMAT. bitmapData[index + x * 3 + 1] = palette[1][color]. for (int j = 2. < numColors.while (sizeOfInfoHeader > 0) { read8BitValue(). } char color = (char) ((byteRead >> (7 . for (int y = 0. x++) { if (x % 8 == 0) { byteRead = read8BitValue(). // BYTE rgbGreen. } // Pad to 32-bit boundery. read8BitValue(). palette[j][i] << bitCount.

int[] y. k < (((width + 1) / 2) % 4). } bitmapData[index + i * 3] = palette[0][color].// and the second to be 0-2. int[] x. Move drawing cursor. return true. private boolean handleEscapeCode(int secondByte. int color. j++) { int index = j * width * 3. private int readBitmapData4Bit(byte[] bitmapData) { if (compression != BI_RGB && compression != BI_RLE4) return ILLEGAL_FILE_FORMAT. for (int k = 0. for (int i = 0. This is only used in RLE-4 and RLE-8 // encoding. Can be uncompressed or RLE-4 compressed. if (y[0] >= height) { res[0] = ILLEGAL_FILE_FORMAT. i++) { if (i % 2 == 0) { byteValue = read8BitValue(). } // Pad to 32-bit boundery. x[0] += read8BitValue(). if (x[0] >= width || x[0] < 0 || y[0] >= height || y[0] < 0) { res[0] = ILLEGAL_FILE_FORMAT. bitmapData[index + i * 3 + 1] = palette[1][color]. } else { color = (byte) (byteValue & 0x0F). } else // secondByte=0x02 { // Delta. k++) read8BitValue(). } (y[0]) ++. return true. j < height. color = (byte) (byteValue >> 4). } } return false. int byteValue = 0. } 221 . int[] res) { if (secondByte == 0x00) { // End of line x[0] = 0. bitmapData[index + i * 3 + 2] = palette[2][color]. // Uncompressed 4 bit encoding if (compression == BI_RGB) { for (int j = 0. } else if (secondByte == 0x01) { // End of bitmap res[0] = SUCCESS. } // Draw a 4-bit image. return true. y[0] += read8BitValue(). i < width.

y. i < secondByte. int color2 = secondByte & 0x0F. if (handleEscapeCode(secondByte. int color. for (int i = 0. // Clear bitmap data since it is legal not to // fill it all out. } bitmapData[index + x[0] * 3] = palette[0][color]. // Is this an escape code or absolute encoding? if (firstByte == 0) { // Is this an escape code if (secondByte <= 0x02) { int[] res = new int[1]. 222 . if (i % 2 == 0) { databyte = read8BitValue(). color = databyte >> 4. bytesRead = 0. } // Pad to 16-bit word boundery while (bytesRead % 2 != 0) read8BitValue(). (x[0]) ++. int databyte = 0. int[] y = new int[1]. x. perform data decode for next // length of colors int color1 = secondByte >> 4. } else { color = databyte & 0x0F. } else { // Absolute encoding int index = y[0] * width * 3.} // RLE encoded 4-bit compression if (compression == BI_RLE4) { // Drawing cursor pos int[] x = new int[1]. i < width * height * 3. res)) return res[0]. i++) bitmapData[i] = 0. int secondByte = read8BitValue(). x[0] = 0. bitmapData[index + x[0] * 3 + 1] = palette[1][color]. for (int i = 0. while (true) { int firstByte = read8BitValue(). y[0] = 0. bitmapData[index + x[0] * 3 + 2] = palette[2][color]. } } else { // If not absolute or escape code. i++) { if (x[0] >= width) return ILLEGAL_FILE_FORMAT.

y[0] = 0. } // Read 8-bit image. j++) { int color = read8BitValue(). while (true) { int firstByte = read8BitValue(). int color. int[] y = new int[1]. if (i % 2 == 0) color = color1. int secondByte = read8BitValue(). i < firstByte. i < height. k++) read8BitValue(). for (int i = 0. i++) { if (x[0] >= width) return ILLEGAL_FILE_FORMAT. bitmapData[index + j * 3 + 2] = palette[2][color]. private int readBitmapData8Bit(byte[] bitmapData) { if (compression != BI_RGB && compression != BI_RLE8) return ILLEGAL_FILE_FORMAT. } } if (compression == BI_RLE8) { // Drawing cursor pos int[] x = new int[1]. x[0] = 0. bitmapData[index] = palette[0][color]. j < width. bitmapData[index + j * 3 + 1] = palette[1][color]. Can be uncompressed or RLE-8 compressed. i++) bitmapData[i] = 0. bitmapData[index + j * 3] = palette[0][color]. int index = y[0] * width * 3 + x[0] * 3. for (int j = 0. // Clear bitmap data since it is legal not to // fill it all out. } } } } return SUCCESS. bitmapData[index + 2] = palette[2][color]. (x[0]) ++. i < width * height * 3. if (compression == BI_RGB) { // For each scan line for (int i = 0.for (int i = 0. for (int k = 0. bitmapData[index + 1] = palette[1][color]. k < (width % 4). else color = color2. i++) { int index = i * width * 3. bytesRead = 0. 223 . } // go to next alignment of 4 bytes.

i < firstByte. } // Load a 24-bit image. bitmapData[index + 1] = palette[1][secondByte]. j++) { bitmapData[index + j * 3 + 2] = (byte) read8BitValue(). k++) read8BitValue(). bitmapData[index + j * 3 + 1] = (byte) read8BitValue(). for (int k = 0. } // go to next alignment of 4 bytes. (x[0]) ++. } // Pad to 16-bit word boundery if (secondByte % 2 == 1) read8BitValue(). } } } } return SUCCESS. Verify this. } } else { // If not. int index = y[0] * width * 3 + x[0] * 3. if (handleEscapeCode(secondByte. bitmapData[index + 2] = palette[2][secondByte]. k < ((width * 3) % 4). for (int j = 0. if (compression != BI_RGB) return ILLEGAL_FILE_FORMAT. bitmapData[index + x[0] * 3 + 2] = palette[2][color]. Cannot be encoded. i++) { int index = i * width * 3. (x[0]) ++. perform data decode for next length of colors for (int i = 0. i < height. bitmapData[index + x[0] * 3 + 1] = palette[1][color]. } else { // Absolute encoding int index = y[0] * width * 3. i < secondByte. res)) return res[0]. j < width. x. for (int i = 0. int color = read8BitValue(). bitmapData[index + x[0] * 3] = palette[0][color]. private int readBitmapData24Bit(byte[] bitmapData) { // 24-bit bitmaps cannot be encoded. bitmapData[index + j * 3] = (byte) read8BitValue(). i++) { if (x[0] >= width) return ILLEGAL_FILE_FORMAT.// Is this an escape code or absolute encoding? if (firstByte == 0) { // Is this an escape code if (secondByte <= 0x02) { int[] res = new int[1]. for (int i = 0. i++) { if (x[0] >= width) return ILLEGAL_FILE_FORMAT. } 224 . y. bitmapData[index] = palette[0][secondByte].

if (res == 0) { // The bitmap data we are going to hand to OpenGL bitmapData[0] = new byte[width * height * 3]. } } // Load the BMP. // The data reading procedure depends on the bit depth. private int loadBMP(InputStream filep. while (bytesRead < byteOffset) read8BitValue(). switch (bitCount) { case 1 : return readBitmapData1Bit(bitmapData). return readBitmapData24Bit(bitmapData). Most images will need no padding // since they already are made to use as little space as // possible. int res = SUCCESS. } return res. case 8 : return readBitmapData8Bit(bitmapData). } if (res == 0) { // Read Data res = readBitmapData(bitmapData[0]).return SUCCESS. case 4 : return readBitmapData4Bit(bitmapData). } } 225 . } private int readBitmapData(byte[] bitmapData) { // Pad until byteoffset. Assumes that no extension is appended to the filename. default : // 24 bit. // Open file for buffered read. // Read Info Header if (res == 0) res = readInfoHeader(). if (bitmapData[0] == null) res = OUT_OF_MEMORY. bitmapData[0] = null. bytesRead = 0. byte[][] bitmapData) { file = filep. // If successful *bitmapData will contain a pointer to the data. // Read Palette if (res == 0) res = readPalette(). // Read File Header if (res == 0) res = readFileHeader().

cpp Matrix. Header file to BoxTree. Feb 2002.h main. Released under LGPL.cpp A Node in the kd-tree used for the cull scene graph. 226 .h Vector.h" <stdio. // // A OpenGL Windows .h general.cpp A small set of general purpose functions.h> <string. Header to Vector.cpp BoxTree.cpp Vector.cpp Frustum. Header file to Frustum.h Frustum.h BoxTree. jacob@marner. short is 16-bit and // char is 8-bit.cpp BMPLoader. Header file to BMPLoader. Contains most of the implementation of the vector. #include #include #include #include #include "BMPLoader. Main application file.cpp ////////////////////////////////////////////////////////////////////////////// // Tweaked virtual world (C++ version) // by Jacob Marner. BMPLoader.Appendix G: Tweaked 3D Demo The 3D demo exists in three versions. a C++ version and a Java version written in GL4Java and a Java version using Java3D. A 4x4 float matrix class A generic float vector class.cpp.BMP loader. // ////////////////////////////////////////////////////////////////////////////// // Copyright 2002 Jacob Marner.dk.cpp The view frustum. Part 1: C++ source code The source code consists of the following files: BMPLoader.h This file contains a BMP texture loader for OpenGL.h> <memory.h> <math.h> // Note: This loader assumes that int is 32-bit.

// UINT bfReserved1. BI_RLE8. // DWORD bfSize. // Reads and returns a 32-bit value from the file. bytesRead+=4. 24. // Image height in pixels static int height. return c1 + (c2<<8) + (c3<<16) + (c4<<24). // Palette used for paletted images during load. // DWORD bfOffBits. // The number of bytes read so far static int bytesRead. bytesRead+=1. return (unsigned char)c1.4 or 8 then // images is paletted. static int bitCount. 8. static unsigned char palette[3][256]. } // Reads and returns a 8-bit value from the file static unsigned char read8BitValue() { unsigned int c1 = fgetc(file). // Number of bit per pixel. // The file that the BMP is stored in. } // In Windows terms read this structure // typedef struct tagBITMAPFILEHEADER { // UINT bfType. int c3 = fgetc(file). static int byteOffset. static FILE* file. static CompressionType compression. Is 1. /* bmfh */ 227 . // } BITMAPFILEHEADER. 4. othewise not. BI_RLE4 }.// Note: Data in the .BMP format is stored in little-endian format. int c2 = fgetc(file). // UINT bfReserved2. } // Reads and returns a 16-bit value from the file static short read16BitValue() { int c1 = fgetc(file). // Image width in pixels static int width. static int read32BitValue() { int c1 = fgetc(file). If it is 1. return c1 + (c2<<8). // Compression enum CompressionType {BI_RGB=0. int c2 = fgetc(file). int c4 = fgetc(file). // The offset from the BITMAPFILEHEADER structure // to the actual bitmap data in the file. bytesRead+=2.

read32BitValue(). compression = (CompressionType)read32BitValue(). According to the specification this // must be 1. return LOAD_TEXTUREBMP_SUCCESS. if (read16BitValue()!=1) return LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. We do not need this. If it is larger we read // some more bytes in the end to be forward compatible. bitCount = read16BitValue(). if (sizeOfInfoHeader<40) return LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. 228 . // Skip the two reserved areas read16BitValue(). // Read image size. if (bitCount != 1 && bitCount != 4 && bitCount != 8 && bitCount != 24) return LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. width = read32BitValue(). unsigned int sizeOfInfoHeader = (unsigned int)read32BitValue(). height = read32BitValue(). // DWORD biClrUsed. // WORD biPlanes. if (compression != BI_RGB && compression != BI_RLE8 && compression != BI_RLE4) return LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. // LONG biWidth. } // In windows terms read this struct: //typedef struct tagBITMAPINFOHEADER { /* bmih */ // DWORD biSize. This is irrelevant since we simply // want the bitmap. // Read file size. // Pixel to device mapping. Ignore. static LOAD_TEXTUREBMP_RESULT readInfoHeader(void) { // We expect this to be at least 40. // LONG biXPelsPerMeter. // Read the byte offset byteOffset = read32BitValue(). if (read8BitValue()!=66 || read8BitValue()!=77) return LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. We do not need this since we have the // image size. // LONG biYPelsPerMeter.static LOAD_TEXTUREBMP_RESULT readFileHeader(void) { // Read "BM" tag in ASCII. read16BitValue(). read32BitValue(). // DWORD biSizeImage. //} BITMAPINFOHEADER. // WORD biBitCount. // DWORD biClrImportant. // Read number of planes. // LONG biHeight. // DWORD biCompression. read32BitValue(). read32BitValue().

bitmapData[index+x*3] = palette[0][color]. static LOAD_TEXTUREBMP_RESULT readPalette(void) { // 24-bit images are not paletted. // Apply padding in end of header to be forward compatible. sizeOfInfoHeader -= 40. We do not need this. } // The palette follows directly after the // info header // //typedef struct tagRGBQUAD { /* rgbq */ // BYTE rgbBlue. int numColors = 1<<bitCount. // } RGBQUAD.i<numColors. } static LOAD_TEXTUREBMP_RESULT readBitmapData1Bit(unsigned char* bitmapData) { // 1-bit format cannot be compressed if (compression!=BI_RGB) return LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. read8BitValue(). read32BitValue(). // BYTE rgbGreen. } unsigned char color = (byteRead >> (7-(x%8))) & 1. sizeOfInfoHeader--.y<height.x<width. for (int i=0. } return LOAD_TEXTUREBMP_SUCCESS.// Read colors used. read32BitValue(). for (int x=0.x++) { if (x%8==0) { byteRead = read8BitValue(). for (int y=0. for (int j=2. bitmapData[index+x*3+1] = palette[1][color].j>=0. while (sizeOfInfoHeader>0) { read8BitValue().i++) { // Read RGB. so it is ignored.y++) { int index = y*width*3. unsigned char byteRead. if (bitCount==24) return LOAD_TEXTUREBMP_SUCCESS. // BYTE rgbReserved. bitmapData[index+x*3+2] = palette[2][color]. // Read the number of important colors. // Skip reversed byte. } return LOAD_TEXTUREBMP_SUCCESS.j--) palette[j][i] = read8BitValue(). } 229 . // BYTE rgbRed. This will not be needed // in OpenGL so we will ignore this.

color = byteValue>>4. Can be uncompressed or RLE-4 compressed. } (*y)++. *x += read8BitValue(). int* y. This is only used in RLE-4 and RLE-8 // encoding. } else if (secondByte==0x01) { // End of bitmap *res = LOAD_TEXTUREBMP_SUCCESS.j<height. for (int i=0. unsigned char color. Move drawing cursor. } else // secondByte=0x02 { // Delta.// Pad to 32-bit boundery. *y += read8BitValue(). int* x.i<width.i++) { if (i%2==0) { byteValue = read8BitValue(). } 230 . while(bytesRead%4 != 0) read8BitValue(). } // This is called after the first byte has been found to be 0 // and the second to be 0-2. unsigned char byteValue. if ((*y)>=height) { *res = LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. } } return false. // Uncompressed 4 bit encoding if (compression==BI_RGB) { for (int j=0. return true. static LOAD_TEXTUREBMP_RESULT readBitmapData4Bit(unsigned char* bitmapData) { if (compression!=BI_RGB && compression!=BI_RLE4) return LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. static bool handleEscapeCode(int secondByte. return true. if (*x>=width || *x<0 || *y>=height || *y<0) { *res = LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. } else { color = byteValue & 0x0F.j++) { int index = j*width*3. return true. } // Draw a 4-bit image. LOAD_TEXTUREBMP_RESULT* res) { if (secondByte==0x00) { // End of line (*x)=0. } return LOAD_TEXTUREBMP_SUCCESS.

0. } else { // Absolute encoding int index = y*width*3. if (i%2==0) { databyte = read8BitValue(). while(true) { unsigned char firstByte = read8BitValue(). for (int k=0. bitmapData[index+x*3+1] = palette[1][color]. k<(((width+1)/2)%4).&x. i++) { if (x>=width) return LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. unsigned char databyte. i<secondByte.&y. } else { color = databyte & 0x0F.bitmapData[index+i*3] = palette[0][color]. memset(bitmapData. for (int i=0. if (handleEscapeCode(secondByte. bytesRead=0. int color. bitmapData[index+i*3+1] = palette[1][color].k++) read8BitValue(). } bitmapData[index+x*3] = palette[0][color]. color = databyte >> 4. unsigned char secondByte = read8BitValue(). x++. // Is this an escape code or absolute encoding? if (firstByte==0) { // Is this an escape code if (secondByte<=0x02) { LOAD_TEXTUREBMP_RESULT res. } } // RLE encoded 4-bit compression if (compression==BI_RLE4) { // Drawing cursor pos int x=0.sizeof(unsigned char)*width*height*3). bitmapData[index+x*3+2] = palette[2][color]. bitmapData[index+i*3+2] = palette[2][color]. } // Pad to 32-bit boundery. } 231 . int y=0. // Clear bitmap data since it is legal not to // fill it all out.&res)) return res. } // Pad to 16-bit word boundery while (bytesRead%2 != 0) read8BitValue().

int color2 = secondByte & 0x0F.i++) { if (x>=width) return LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT.sizeof(unsigned char)*width*height*3). k<(width%4). bitmapData[index+j*3+1] = palette[1][color]. bitmapData[index+j*3+2] = palette[2][color]. x++. static LOAD_TEXTUREBMP_RESULT readBitmapData8Bit(unsigned char* bitmapData) { if (compression!=BI_RGB && compression!=BI_RLE8) return LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. for (int j=0. if (i%2==0) color = color1. } // go to next alignment of 4 bytes.j++) { int color = read8BitValue(). bytesRead=0. bitmapData[index+2] = palette[2][color]. bitmapData[index+j*3] = palette[0][color]. int color. } // Read 8-bit image. bitmapData[index] = palette[0][color].k++) read8BitValue(). memset(bitmapData. else color = color2. Can be uncompressed or RLE-8 compressed.i<height. bitmapData[index+1] = palette[1][color]. for (int k=0.i<firstByte. } } if (compression==BI_RLE8) { // Drawing cursor pos int x=0.0. } } } } return LOAD_TEXTUREBMP_SUCCESS. int index = y*width*3+x*3.j<width. int y=0. for (int i=0. // Clear bitmap data since it is legal not to // fill it all out. while(true) 232 .i++) { int index = i*width*3. perform data decode for next // length of colors int color1 = secondByte >> 4. if (compression==BI_RGB) { // For each scan line for (int i=0.} else { // If not absolute or escape code.

&res)) return res.i++) { int index = i*width*3.j++) { bitmapData[index+j*3+2] = read8BitValue(). } } else { // If not.i<height. perform data decode for next length of colors for (int i=0. for (int i=0. Verify this. } // Pad to 16-bit word boundery if (secondByte%2 == 1) read8BitValue(). if (handleEscapeCode(secondByte. i++) { if (x>=width) return LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT.{ unsigned char firstByte = read8BitValue(). bitmapData[index+j*3+1] = read8BitValue(). for (int i=0. bitmapData[index+x*3] = palette[0][color]. int color = read8BitValue(). Cannot be encoded. x++.j<width. i<secondByte. // Is this an escape code or absolute encoding? if (firstByte==0) { // Is this an escape code if (secondByte<=0x02) { LOAD_TEXTUREBMP_RESULT res. x++. bitmapData[index+x*3+1] = palette[1][color]. bitmapData[index] = palette[0][secondByte]. bitmapData[index+1] = palette[1][secondByte]. } // Load a 24-bit image. for (int j=0. bitmapData[index+x*3+2] = palette[2][color]. unsigned char secondByte = read8BitValue(). } 233 . } } } } return LOAD_TEXTUREBMP_SUCCESS. bitmapData[index+2] = palette[2][secondByte].i<firstByte. bitmapData[index+j*3] = read8BitValue().i++) { if (x>=width) return LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. if (compression!=BI_RGB) return LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. static LOAD_TEXTUREBMP_RESULT readBitmapData24Bit(unsigned char* bitmapData) { // 24-bit bitmaps cannot be encoded.&x.&y. int index = y*width*3+x*3. } else { // Absolute encoding int index = y*width*3.

bmp"). } // Read File Header if (!res) res=readFileHeader(). LOAD_TEXTUREBMP_RESULT loadBMP(const char* filename. // Read Palette 234 .filename). if (!str) return LOAD_TEXTUREBMP_OUT_OF_MEMORY. // Open file for buffered read. char readBuffer[BUFSIZ]. return readBitmapData24Bit(bitmapData). if (!file) res = LOAD_TEXTUREBMP_COULD_NOT_FIND_OR_READ_FILE.". // Read Info Header if (!res) res=readInfoHeader()."rb"). case 4: return readBitmapData4Bit(bitmapData). readBuffer). unsigned char** bitmapData) { *bitmapData = NULL. // The data reading procedure depends on the bit depth. Most images will need no padding // since they already are made to use as little space as // possible.k++) read8BitValue(). strcat(str. default: // 24 bit. // Append . } } // Load the BMP. } static LOAD_TEXTUREBMP_RESULT readBitmapData(unsigned char* bitmapData) { // Pad until byteoffset. file = fopen(str. } return LOAD_TEXTUREBMP_SUCCESS.bmp extension char* str = new char[strlen(filename)+5]. k<((width*3)%4).// go to next alignment of 4 bytes. while(bytesRead<byteOffset) read8BitValue(). LOAD_TEXTUREBMP_RESULT res = LOAD_TEXTUREBMP_SUCCESS. switch(bitCount) { case 1: return readBitmapData1Bit(bitmapData). bytesRead=0. case 8: return readBitmapData8Bit(bitmapData). strcpy(str. for (int k=0. if (!res) { setbuf(file. Assumes that no extension is appended to the filename. // If successful *bitmapData will contain a pointer to the data.

} // Clean up delete[] str. } if (!res) { // Read Data res=readBitmapData(*bitmapData). char* str = new char[strlen(filename)].".bmp") || !strcmp(withext+i. 1). if (!res) { // The bitmap data we are going to hand to OpenGL *bitmapData = new unsigned char[width*height*3]. glPixelStorei(GL_UNPACK_ALIGNMENT. str).j<i. static void removeBMPextension(const char* withext. } // Removes the . glGenTextures(1.if (!res) res=readPalette().BMP")) { for (int j=0. } } } // The main function. removeBMPextension(filename.bmp extension of the filename if it exists.i>=0. // Only clean up bitmapData if there was an error. GLint internalformat) { LOAD_TEXTUREBMP_RESULT res. glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT). *textureName).j++) { result[j] = withext[j]. if (*bitmapData && res) delete[] *bitmapData. glBindTexture(GL_TEXTURE_2D. // load bmp 235 . LOAD_TEXTUREBMP_RESULT loadOpenGL2DTextureBMP(const char* filename. return res. return.textureName). GLuint *textureName. if (file) fclose(file). This is the only one that is exported.". if (!str) return LOAD_TEXTUREBMP_OUT_OF_MEMORY. if (!bitmapData) res = LOAD_TEXTUREBMP_OUT_OF_MEMORY. } result[i]='\0'.i--) { if (!strcmp(withext+i. char* result) { for(int i=strlen(withext)-1.

GL_RGB. LOAD_TEXTUREBMP_ILLEGAL_FILE_FORMAT. enum LOAD_TEXTUREBMP_RESULT { // The texture loading operation succeeded. // The file does not comply with the specification. The status of the operation will be returned as the return value. } // Clean up. If you need the bitmap for anything else than // textures. or you need borders you will have to edit the source code. texturename is undefined. #include <GL/glut. LOAD_TEXTUREBMP_COULD_NOT_FIND_OR_READ_FILE.textureName). internalformat. } BMPLoader. // // An opengl . return res. // ////////////////////////////////////////////////////////////////////////////// // Copyright 2002 Jacob Marner. if (bitmapData) delete[] bitmapData.unsigned char* bitmapData. LOAD_TEXTUREBMP_OUT_OF_MEMORY}. #ifndef BMP_LOADER_HEADER #define BMP_LOADER_HEADER // Does you program not use GLUT? If not. LOAD_TEXTUREBMP_OPENGL_ERROR. // The file could not be found or it could not be opened for reading. res = loadBMP(str. jacob@marner. If an error occured.BMP texture loader.h> and <GL/glu. remove the following // include line and include the standard OpenGL headers instead. Use this to // get information about how the loading operation went. whose name will be returned in textureName.h ////////////////////////////////////////////////////////////////////////////// // Tweaked virtual world (C++ version) // by Jacob Marner. } if (glGetError()!=GL_NO_ERROR) { res = LOAD_TEXTUREBMP_OPENGL_ERROR. LOAD_TEXTUREBMP_SUCCESS=0. delete[] str.h>. if (!res) { gluBuild2DMipmaps(GL_TEXTURE_2D.h> also. // The system ran out of heap memory during the texture load. // If you use Windows remember to include <windows.dk.&bitmapData). width. Feb 2002. height. // load texture into OpenGL with mip maps and scale it if needed.h> // The following is the function return type.bitmapData). // The bitmap will be loaded without borders and all mip maps will be // generated automatically. Released under LGPL. // // // // Loads the specified BMP image and stores it into a OpenGL 2D texture. // OpenGL could not accept the texture. 236 . You proably used a internal // format not accepted by your OpenGL implementation or you may have run // out of available texture names. // <GL/gl. if (res) glDeleteTextures(1. GL_UNSIGNED_BYTE. glPopClientAttrib().

The actual file *must* use the . GLuint* textureName. n. LOAD_TEXTUREBMP_RESULT loadOpenGL2DTextureBMP(const char* filename.z. For the filename string given as parameter the extension is optional . // // A node in the kd-tree used for culling. #endif BoxTree. Must be Windows . 237 . float splitvalue). // ////////////////////////////////////////////////////////////////////////////// #include <iostream. int static int reorderz(BoxData boxData[].BMP format. int ny = normal.y.x. if (isOutside(boundingBox[nx]. Defaults to GL_RGB. const Vector& point.z >= 0 ? 1 : 0. n. If the return value is not LOAD_TEXTUREBMP_SUCCESS then this value is undefined. return normal. const Frustum& frustum) { // Find corner furthest from plane in normals direction. Feb 2002. If you later want to delete the loaded texture call glDeleteTextures() on the returned texture name. [in] internalformat: The internal format of the loaded texture. Is passed directly to glTextImage2D().dot(v)>=0.y >= 0 ? 1 : 0. const Vector& normal) { Vector v(x.x >= 0 ? 1 : 0.h> #include "BoxTree.y. boundingBox[nz]. float splitvalue). float y.points[plane]. bool Node::basicCullTest(int plane.cpp ////////////////////////////////////////////////////////////////////////////// // Tweaked virtual world (C++ version) // by Jacob Marner. float z.z). static inline bool isOutside(float x. int nz = normal. // Check if corner furthest from plane in normals direction is outside. // If not it is fully inside.h" #include "Matrix. This can immediately be used with glBindTexture() to use the texture during drawing.h" // Reorder the box data in place according // values below split value comes first. int static int reordery(BoxData boxData[]. GLint internalformat = GL_RGB).normals[plane].x such that all with n. Return value: [out] Status of operation. frustum. v-=point. normal)) { // Check if corner closest to plane in normal's direction is outside. [in/out] textureName: Pass a pointer to an already allocated GLuint and if the operation success then this data will point to the texture name of the texture object loaded. } static bool isInside. const Vector& normal = frustum.if it is not there it is appended automatically.// // // // // // // // // // // // // // // // Parameters: [in] filename: The name of the texture file. boundingBox[ny]. float splitvalue). int to position. static int reorderx(BoxData boxData[].bmp extension. int nx = normal.

x. These are used to do a bounding sphere // culling. if (isInside) { glCallList(displayList).center). if (basicCullTest(lastPlaneCulled. if (basicCullTest(plane. return. } } // Is the box fully within the frustum? If yes. the box is fully outside. plane++) { if (plane==lastPlaneCulled) continue. for (plane=0. We only test to points // per bounding box for each plane.frustum. boundingBox[1-nz]. We remember what plane // was used to cull this the last time and we start with // that. plane<6. boundingBox[1-ny]. consisting of // 1. } else isInside=false. Temporal coherance culling. the box is intersecting the fustrum border. } return false.// If yes. // If no. return. } // We do a frustum culling algorithm. 238 .y.radiusSquared + boxRadiusSquared) return. // If it is a leaf draw it all. Plane to point comparison to see of the box // is entirely outside the plane.getLengthSquared() > frustum. // 3.z. int plane. if ((boxCenter . void Node::render(const Frustum& frustum) { isInside = true.points[plane]. // Note: We do not use the octant test to reduce the plane // comparisons needed to 3 since that requires a symmetric // frustum. if (isOutside(boundingBox[1-nx].frustum)) { lastPlaneCulled = plane. if not go to children nodes.frustum)) return. if (leftChild==NULL) { glCallList(displayList). } // We are now in doubt whether the box should be drawn. // 2. simply render the // display list. normal)) { return true. frustum. Bounding spheres for the frustum and bounding box // is precalculated. } else { leftChild->render(frustum).

z = candidate.y = candidate. w.z). // Find and set bounding box by iterating through boxes. // (Boxes is centered about position and has a distance // from center of max sqrt(2*pow(scale.y) boundingBox[1]. boundingBox[1] = Vector(0. lastPlaneCulled(0) { boundingBox[0] = Vector(WORLD_MAX_X. candidate = boxData[c].posy. } } void Node::transformAndDrawVertex(const Matrix& m. float candidate.2)). } // The given normal is assumed to be normalized already.z). void Node::transformAndSetNormal(const Matrix& t. float posy.texy).y .y) boundingBox[0].z + boxMaxDistFromCenter. float posz) { glTexCoord2f(texx. candidate = boxData[c].y = candidate.v.v. candidate = boxData[c]. v.WORLD_MAX_Y. float texy. candidate = boxData[c]. c<n.0).x + boxMaxDistFromCenter. // If n is more than max_boxes if (n>MAX_BOXES_PER_LEAF) { int splitn.boxMaxDistFromCenter. v.MultLeftMatrix(m).x.boxMaxDistFromCenter.y + boxMaxDistFromCenter. ++c) { float boxMaxDistFromCenter = sqrt(2*pow(boxData[c]. float posx.y. candidate = boxData[c].x.position.x) boundingBox[0].0.x = candidate. candidate = boxData[c].posz). if (candidate < boundingBox[0].x) boundingBox[1].WORLD_MAX_Z).z = candidate.z). if (candidate < boundingBox[0].y. rightChild(NULL). glNormal3f(v. int n) : leftChild(NULL). if (candidate > boundingBox[1].x = candidate.rightChild->render(frustum).position.2)) ) for (int c=0. boxRadiusSquared = (boxCenter .y. displayList(0). if (candidate < boundingBox[0].z) boundingBox[1]. float y.normalize().position.x . if (candidate > boundingBox[1].position. float z) { Vector v(x.w. } boxCenter = (boundingBox[1]-boundingBox[0])/2 + boundingBox[0].position.z) boundingBox[0]. glVertex3f(w.boxMaxDistFromCenter.getLengthSquared().position.z . float texx.MultLeftMatrixIgnoreW(t).boundingBox[0]). float x. if (candidate > boundingBox[1]. Vector w(posx.w. 239 . } Node::Node(BoxData boxData[].scale.

x.y . // Reorder box data according to center on the max axis.n.z.if (n>2) { float xspan = boundingBox[1]. ASSERT(displayList).y. if (splitn==n) splitn = n-1. glEndList().++i) { Matrix m.n. splitn). ASSERT(displayList). n-splitn). GL_COMPILE).n.y.boundingBox[0].0f. This display list // must be executed within a begin() end() block that was started // with GL_QUAD_STRIP displayList = glGenLists(1). glCallList(leftChild->displayList). // Reorder box data according to center on the max axis.translate(boxData[i]. glNewList(displayList.y + yspan/2. splitn = reorderx(boxData. } } } else { splitn=1. } else { if (yspan >= zspan) { // Find center along max axis float center = boundingBox[0]. float yspan = boundingBox[1]. GL_COMPILE).center). } else { // Find center along max axis float center = boundingBox[0]. } if (splitn==0) splitn = 1.x .position. for (int i=0.i<n. if (xspan >= yspan && xspan >= zspan) { // Find center along max axis float center = boundingBox[0].z . 240 . // Initialize display list that calls children display list displayList = glGenLists(1). // Reorder box data according to center on the max axis.z + zspan/2.x + xspan/2.x.loadIdentity().0f. rightChild = new Node(&(boxData[splitn]). splitn = reorderz(boxData. splitn = reordery(boxData.boundingBox[0].center). // Create nodes for each child leftChild = new Node(boxData.center).0f. float zspan = boundingBox[1]. glNewList(displayList. m. } else { // Initialize display list to draw the boxes.position. glCallList(rightChild->displayList). m.boundingBox[0].boxData[i].

scale. transformAndDrawVertex(m.1.1.boxData[i].-1.0. t.1.scale.1.1.1. // Right side transformAndSetNormal(t.rotate(boxData[i].1).0.-1).0). transformAndDrawVertex(m.-1.-1. transformAndDrawVertex(m.0.1.0.-1.1.-1).-1).1.1. transformAndDrawVertex(m.y.rotate(boxData[i].0.1.1.1).scale(scale.1). // Left side transformAndSetNormal(t.1.0.0. transformAndDrawVertex(m.0. ASSERT(rightChild).1.-1.1.1).1.-1.1.0.1).-1.1. // Front side transformAndSetNormal(t.1.0.1). } glDeleteLists(displayList.1.0). float splitvalue) \ 241 .1.1.-1.0.0.1).0).1.1.-1).0. transformAndDrawVertex(m.0.-1).-1.rotation.1.rotation.-1.scale). transformAndDrawVertex(m.1.z.0.1.0.z).-1.1.1. transformAndDrawVertex(m.1. int n. transformAndDrawVertex(m.-1.-1).1. } glEndList().0. transformAndDrawVertex(m.-1.rotate(boxData[i]. } #define REORDER(X) \ static int reorder ## X(BoxData boxData[]. transformAndDrawVertex(m.1. transformAndDrawVertex(m.0.1).-1.1.1.-1.0).0.0.-1. transformAndDrawVertex(m.-1.0.-1.0.-1.1.x. transformAndDrawVertex(m.0.-1. m.-1.-1.1). transformAndDrawVertex(m. Matrix t(m).1). m. transformAndDrawVertex(m.1). transformAndDrawVertex(m. transformAndDrawVertex(m. transformAndDrawVertex(m.0.1. transformAndDrawVertex(m.transpose(). m.1.-1).-1.1.1).0.-1.1.1.1).-1).0.0).invert()).-1). transformAndDrawVertex(m.0.1. // Top side transformAndSetNormal(t.0.0.0. m. delete rightChild. // Back side transformAndSetNormal(t.1.1.1.-1. transformAndDrawVertex(m.-1).0.1.1.-1).1).0.0).-1).1.0.-1.1. VERIFY(t.1.0.1.0. // Under side transformAndSetNormal(t.-1).1.1). } } Node::~Node(void) { if (leftChild) { delete leftChild. float scale = boxData[i].1.0.rotation.position. transformAndDrawVertex(m. transformAndDrawVertex(m.

## X>splitvalue) j--.h #ifndef BOXTREE_LIBRARY #define BOXTREE_LIBRARY #include #include #include #include "Vector. }.position.h" "Frustum. j--. } else { while (j<r && boxData[j]. while (j>=p && boxData[j]. int r = n-1.position. boxData[j] = temp. ## X<splitvalue) i++. } } } REORDER(x) REORDER(y) REORDER(z) \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ BoxTree.h" "Matrix. boxData[i] = boxData[j]. while(i<=r && boxData[i]. while (true) { i++. Vector position. return j. Vector rotation.{ if (n<1) return 0. int j = r+1. if (i<j) { BoxData temp = boxData[i]. Feb 2002. int p = 0.h" <GL/glut. int i = p-1. ////////////////////////////////////////////////////////////////////////////// // Tweaked virtual world (C++ version) // by Jacob Marner. // 242 .h> #define WORLD_MAX_X 100 #define WORLD_MAX_Y 100 #define WORLD_MAX_Z 100 #define MAX_BOXES_PER_LEAF 1 struct BoxData { float scale.position. if (j<=p) j=p. ## X<splitvalue) j++.

n is the size of the array. Vector cameraDirection. private: void transformAndDrawVertex(const Matrix& m. void render(const Frustum& frustum). GLuint displayList. float y. Node(BoxData boxData[].normalize(). Vector centerOfNearPlane = cameraPos + cameraDirection * pnear. Node* leftChild. // The camera is in the plane of the sides. float x. // Points for the near anf far planes are found by intersection 243 . float z). ~Node(). Vector boxCenter. bool basicCullTest(int plane. GLdouble bottom. float posz). GLdouble right. Node* rightChild. for (i=0.cross(cameraUp.cpp ////////////////////////////////////////////////////////////////////////////// // Tweaked virtual world (C++ version) // by Jacob Marner. Vector cameraUp.normalize(). int i. float texy. // First min box then max box Vector boundingBox[2]. float boxRadiusSquared.cross(cameraDirection)). GLdouble pnear. const Frustum& frustum). float posx.h" Frustum::Frustum(Vector cameraPos. cameraUp. int n).// A node in the kd-tree used for culling. #endif Frustum.i++) points[i] = cameraPos. GLdouble pfar) { // Make sure that all the direction vectors are unit vectors. Feb 2002. cameraUp = cameraDirection. // ////////////////////////////////////////////////////////////////////////////// #include "Frustum. void transformAndSetNormal(const Matrix& m.i<4. It is now paralell to the near plane. float posy. float texx. cameraDirection. int lastPlaneCulled. // Make sure that the up vector is perpendular to the // cameraDirection. GLdouble left. }. // ////////////////////////////////////////////////////////////////////////////// class Node { public: // This method may reorder the ordering of the // box data array. GLdouble top. // // The view fustrum.

h> #include "Vector.right) or the perpendular of the upvector (top.cross(cameraUp) . 2. 3.cameraDirection.cameraPos).cameraPos).cameraPos). crossed with the upvector (left. Feb 2002. normals[1] = (rightNearPoint . normals[3] = cameraDirection. 244 . By moving along the near plane with the given amount we get another point in the plane.-left).bottom) give us the normal of the plane. // ////////////////////////////////////////////////////////////////////////////// #ifndef FRUSTUM_LIBRARY #define FRUSTUM_LIBRARY #include <GL/glut.(leftNearPoint .cross(bottomNearPoint .cross(cameraUp) * right + centerOfNearPlane.right. // Calculate normal for left side Vector leftNearPoint = cameraDirection. normals[2] = . points[5] = cameraPos + cameraDirection * pfar.cross(cameraUp) . The general scheme is: 1. // // The view fustrum. points[4] = centerOfNearPlane. radiusSquared = (extremeFarCorner . // Calculate the center of the frustum center = (points[5] .h ////////////////////////////////////////////////////////////////////////////// // Tweaked virtual world (C++ version) // by Jacob Marner.center).cross(cameraUp).h" class Frustum { public: // points and normals for planes in this order: // left.cameraPos) / pnear) * pfar + cameraPos.cross(cameraUp) * left + centerOfNearPlane.cross(cameraUp). // Calculate normal for top side Vector topNearPoint = centerOfNearPlane + cameraUp * top.near. normals[4] = -cameraDirection. // Set normals for near and far planes.points[4])/2 + points[4]. } Frustum. // Normal points towards camera normals[5] = cameraDirection. // Calculate normal for bottom side Vector bottomNearPoint = centerOfNearPlane + cameraUp * bottom.cameraPos). normals[0] = .cross(cameraUp) * max(right.// viewing direction line with plane.far.-bottom) + cameraDirection. Vector extremeNearCorner = centerOfNearPlane + cameraUp * max(top.top. // Normal points away from camera // // // // // // // Calculate normals for sides.cross(topNearPoint . We have one point on the plane as the cameraPos. These two points can. // Calculate normal for right side Vector rightNearPoint = cameraDirection.getSquaredLength(). Vector extremeFarCorner = ((extremeNearCorner .bottom. Vector points[6].

// The last 6 is the parameters given to glFrustum(). float radiusSquared. GLdouble pfar).h> <stdlib. GLdouble left. // // A set of general purpose functions. } #endif #ifndef min template <class Type> Type min(const Type a. GLdouble right. GLdouble bottom. both inclusive. #endif general. const Type b) { return a>b ? b : a. GLdouble top. Frustum(Vector cameraPos.const Type b) { return a<b ? a : b. GLdouble pnear. Vector cameraDirection.Vector normals[6]. } #endif // Return the absolute value of a template <class Type> Type Abs(const Type a) { return a<0 ? -a : a. Feb 2002.h> #ifdef _DEBUG #define ASSERT(X) assert(X) #else #define ASSERT(X) #endif #ifdef _DEBUG #define VERIFY(X) assert(X) #else #define VERIFY(X) X #endif #ifndef max template <class Type> Type max(const Type a.h> <stdio.h ////////////////////////////////////////////////////////////////////////////// // Tweaked virtual world (C++ version) // by Jacob Marner. // ////////////////////////////////////////////////////////////////////////////// #ifndef GENERAL_LIB #define GENERAL_LIB #include #include #include #include <assert. Vector center. Vector cameraUp.h> <string. }. // The first three args is information about the camera in the world. } // Return a random int number between low and high. 245 .

int high) { if (low>high) { int temp = low. low = high. return num.argv[i]. ASSERT(num<=high).argv[i].argv[i].inline int randomInteger(int low. low = high. return num. int argc.i<argc.0)) * (high-low) + low). char* argv[]) { for(int i=0.strlen(label))) 246 .i++) { if (!strncmp(label. } } return false. If label was not found return defaultvalue. high = temp.strlen(label))) { return true.i++) { if (!strncmp(label. } } return defaultvalue. } // Search a set of command line arguments for one beginning with label. ASSERT(num>=low). inline float getFloatArgument(const char* label.0)) * (high-low+1) + low). int defaultvalue) { for(int i=0. char* argv[]. inline bool getBooleanFlag(const char* label.i++) { if (!strncmp(label. // If it exist return true. ASSERT(num>=low). float high) { if (low>high) { float temp = low. inline int getIntegerArgument(const char* label.i<argc. } int num = (int)((((double)rand())/(((double)RAND_MAX)+1. char* argv[]. } // Search a set of command line arguments for one beginning with label. int argc. // If found parse the ramainder of the string as an integer and return // the value. } // Search a set of command line arguments for one beginning with label. } float num = (float)((((double)rand())/(((double)RAND_MAX)+1.i<argc. int argc. ASSERT(num<=high). high = temp. float defaultvalue) { for(int i=0.strlen(label))) { return atoi(argv[i]+strlen(label)). If label was not found return defaultvalue. // If found parse the ramainder of the string as a float and return // the value. } // Return a random floating point number between low and high // both inclusive inline float randomFloat(float low.

{ char* s = argv[i]+strlen(label). Default: 1000. int height) 247 . } #endif main.h> <iostream. void startTimer(void) { starttime = clock(). return f." << endl.cpp ////////////////////////////////////////////////////////////////////////////// // Tweaked Virtual World (C++ version) // by Jacob Marner.&f). // ////////////////////////////////////////////////////////////////////////////// #include <GL/glut. cout << "Average frame rate: " << ((float)numFrames)/timeelapsed << " fps. frustumTop. Start this to run the application. frustumBottom. Feb 2002.starttime)) / CLOCKS_PER_SEC.h" #ifdef _WIN32 #include <windows. // // The main class.h" <time. frustumNear.h> #include #include #include #include #include #include #include "general.h> "BoxTree. // -boxes:<int> : The number of boxes to generate.h" "BMPLoader. void reshape(int width. } GLdouble GLdouble GLdouble GLdouble GLdouble GLdouble frustumLeft. int numFrames. cout << "Elapsed time: " << timeelapsed << " sec. float f. } void stopTimer(void) { float timeelapsed = ((float)(clock() . sscanf(s. // // The application takes two optional parameters: // -frames:<int> : The number of frames to run the animation.h" "Vector."%f". clock_t starttime. GLuint tex. } } return defaultvalue. Default: 3000. frustumRight.h> #endif Node* root.h" "Frustum. frustumFar = 40. int frame = 0." << endl.

0f.0. upVector.5. glMatrixMode(GL_PROJECTION).GL_DIFFUSE.0 }. glutSwapBuffers(). GLfloat ambient_light[] = {1. glMatrixMode(GL_MODELVIEW).z). GLfloat specular_light[] = {0. cameraPos.GL_AMBIENT. gluLookAt(cameraPos. // If final frame has been drawn stop timer.x+cameraDirection. 1. glMaterialfv(GL_FRONT.0. 0. 1. // Set view matrix glLoadIdentity(). frustumRight = 0. GLfloat diffuse_light[] = {1.0.GL_SPECULAR.x. // Calculate camera position and direction float angle = (float)frame / 600.0. WORLD_MAX_Y/2. frustumLeft = -0.GL_POSITION.0).0.GL_SPECULAR. 1.x. frustumBottom. 1.0 }.x. cameraPos. Vector cameraPos(WORLD_MAX_X/2.mat_specular).0. height).0}.sin(viewAngle)).0+radius*sin(angle)). 1.5*((double)height/(double)width). frustumNear = 0.0. glBegin(GL_QUADS). frustumNear.0. 1.0. 0. float viewAngle = angle+3.0.mat_shininess). if (frame>=numFrames) { stopTimer().14f/2. GLfloat mat_shininess[] = { 50. root->render(frustum).{ glViewport(0. glFrustum(frustumLeft. glLoadIdentity(). 0. frustumTop.ambient_light). glLightfv(GL_LIGHT0. } Vector upVector(0. width. glLightfv(GL_LIGHT0. 0.frustumRight. WORLD_MAX_Z/2.light_position).y. frustumBottom = -0. 1. upVector. frustumTop = 0. 0.z. frustumTop.0f.frustumBottom. 1. glMaterialfv(GL_FRONT. -1.0}.0. frustumFar). glEnd(). 1.0.1. 1.0 }. GLfloat mat_specular[] = { 1.y+cameraDirection.diffuse_light).0.0f.0}.0f+radius*cos(angle).5. frustumRight. upVector.0. cameraDirection.cameraPos.z+cameraDirection. frustumLeft.y. Frustum frustum(cameraPos. GLfloat light_position[] = {0. float radius = 20.0. glLightfv(GL_LIGHT0. 248 .specular_light).cameraPos.z.GL_SHININESS.0.y. cameraPos. frame++.upVector.5*((double)height/(double)width).frustumFar). Vector cameraDirection(cos(viewAngle).frustumNear. glLightfv(GL_LIGHT0. print results and exit. void display(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT).5.

Generate each number // randomly.rotation.rotation. boxData[i]. } // // // // // // Create a tree structure.WORLD_MAX_Y). The leaf nodes push viewmodel matrix. // Generate numBoxes box data put them in array.exit(0). 480). argv. GL_NICEST).1.360). delete[] boxData.y= randomFloat(0. boxData[i].rotation.WORLD_MAX_X).position. int numBoxes = getIntegerArgument("-boxes:". pos(3). boxData[i]. // Create a 640x480 window glutInitWindowSize(640. char *argv[]) { /////////////////////////////////////////////////// // Initialize OpenGL /////////////////////////////////////////////////// glutInit(&argc. argc. boxData[i].WORLD_MAX_Z). 249 .position. scale. } int main(int argc. numBoxes).i<numBoxes. } void init(int numBoxes) { int i. glHint(GL_PERSPECTIVE_CORRECTION_HINT. glutCreateWindow("C++ OpenGL benchmark"). For each one // store scale(1). srand(time(NULL)). rotation (as one matrix!) draw the stripped box and then pop matrix again. Each node contains are display list printing all contents of that node as one diplaylist. 100). } } void idle(void) { glutPostRedisplay(). pos. numFrames = getIntegerArgument("-frames:". 1000).z= randomFloat(0. glutInitWindowPosition(0.++i) { boxData[i]. // Tell OpenGL to use double buffering and // 16/32 bit colors (whatever is the default) glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE). root = new Node(boxData.y= randomFloat(0.z= randomFloat(0.position. Inner nodes use call list to sub node display lists.x= randomFloat(0.x= randomFloat(0. BoxData* boxData = new BoxData[numBoxes]. rotation(3). for (i=0.scale= randomFloat(0.argv. atexit(exitfunc).1. Each node has bounding box information and zero or two children. boxData[i]. argv). } void __cdecl exitfunc( void ) { delete root.360).5). boxData[i]. argc. 0).360).

glFogf(GL_FOG_END.0f.GL_TEXTURE_ENV_MODE. wglSwapIntervalEXT(0). // Register call back functions glutReshapeFunc(reshape).GL_LINEAR). GL_LINEAR). glFogi(GL_FOG_MODE. // (I. GL_DONT_CARE). glutMainLoop(). #ifdef _WIN32 // Turn off vertical screen sync under Windows.85).&tex.e. 0. typedef BOOL (WINAPI *PFNWGLSWAPINTERVALEXTPROC)(int interval). // Tell OpenGL to use flat shading glShadeModel(GL_FLAT). PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL.bmp". glTexParameteri(GL_TEXTURE_2D. #endif // Create data and display lists init(numBoxes).GL_TEXTURE_MIN_FILTER.35). ASSERT(wglSwapIntervalEXT). 0." << endl. glTexParameteri(GL_TEXTURE_2D. glClearColor(0. glFogfv(GL_FOG_COLOR. frustumFar * 0. glFogf(GL_FOG_START. 0.0f. 0. // Enable back face culling. 0). // Load texture if (loadOpenGL2DTextureBMP("glow. // Blend lighting a texture effects. glEnable(GL_LIGHTING). it uses the WGL_EXT_swap_control extension) // On under systems we just live with the default. // Enable fogging glEnable(GL_FOG). GL_MODULATE). fogColor). return 0. glCullFace(GL_BACK). } 250 . wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT"). glutIdleFunc(idle).GL_LINEAR_MIPMAP_LINEAR). GLfloat fogColor[4] = {0. } // Enable texture mapping glEnable(GL_TEXTURE_2D). // Enable Phong lighting model and one global // directional light.0f}. // Enable trilinear filtering. glFogf(GL_FOG_DENSITY.// Turn on z-buffer for hidden surface removal glEnable(GL_DEPTH_TEST). glHint(GL_FOG_HINT. glEnable(GL_CULL_FACE). 0. frustumFar).0f. glutDisplayFunc(display). glEnable(GL_LIGHT0). exit(1). glTexEnvf(GL_TEXTURE_ENV.GL_TEXTURE_MAG_FILTER. startTimer().GL_RGB)) { cerr << "Error loading texture. 0.

float m14. m[15] = m15. m[10] = m10.float m4. } Matrix(const Matrix& ma) { memcpy(m. float m15) { m[0] = m0.float m11.16*sizeof(float)). 251 .float m1. m[1]=m[2]=m[3]=m[4]=m[6]=m[7]=m[8]=m[9]=m[11]=m[12]=m[13]=m[14]=0. m[3] = m3. // // A 4x4 float matrix class.float m13. Feb 2002.h> template<class Type> inline float toRadians(Type deg) { return deg*3. } Matrix(float m0.1415926535f/180.float m2. m[14] = m14. } class Matrix { public: float m[16]. so you can use // glLoadMatrix() on m to import the matrix into // OpenGL.float m8. m[8] = m8. m[13] = m13. m[9] = m9.Matrix.float m3. float m5. m[6] = m6.0f. m[7] = m7.h ////////////////////////////////////////////////////////////////////////////// // Tweaked virtual world (C++ version) // by Jacob Marner. m[5] = m5. m[1] = m1. Matrix(void) { } void loadIdentity() { m[0]=m[5]=m[10]=m[15]=1. // // The internal matrix representation is directly // compatible with that of OpenGL.float m12. m[2] = m2. m[12] = m12. // ////////////////////////////////////////////////////////////////////////////// #ifndef MATRIX_CLASS #define MATRIX_CLASS #include <math. m[11] = m11.m.float m9.ma. // // All operations are applied such that the // Matrix itself is the Left operator. float m10.float m7.float m6. m[4] = m4.

return *this. xz = x*z. t[1] = m[1] * n[0] + m[5] * n[1] + m[9] * n[2] + m[13] * n[3]. float x. x/=length. t[0]= m[0] * n[0] + m[4] * n[1] + m[8] * n[2]. s = (float)sin(rad). n[2] = xz*oneminusc-ys.12*sizeof(float)). float float float float float float float float float c = (float)cos(rad). ys = y*s. n[0] = x*x*oneminusc+c. } float rad = toRadians(a). m[14] = m[2] * n[12] + m[6] * n[13] + m[10] * n[14] + m[14] * n[15]. } void rotate(float a. if (slength!=1. float z) { // Normalize axis vector float slength = x*x+y*y+z*z. m[15] = m[3] * n[12] + m[7] * n[13] + m[11] * n[14] + m[15] * n[15].m. n[6] = yz*oneminusc+xs. n[5] = y*y*oneminusc+c. t[2] = m[2] * n[0] + m[6] * n[1] + m[10] * n[2] + m[14] * n[3]. oneminusc = 1. t[4] = m[0] * n[4] + m[4] * n[5] + m[8] * n[6] + m[12] * n[7].16*sizeof(float)). z/=length.m. memcpy(m.} Matrix& operator=(const Matrix& ma) { memcpy(m. t[9] = m[1] * n[8] + m[5] * n[9] + m[9] * n[10] + m[13] * n[11]. xs = x*s. n[1] = xy*oneminusc+zs. zs = z*s.0f) { float length = (float)sqrt(slength). /* m[12] to m[15] are not changed by a rotate */ float t[8]. m[12] = m[0] * n[12] + m[4] * n[13] + m[8] * n[14] + m[12] * n[15].c. t[5] = m[1] * n[4] + m[5] * n[5] + m[9] * n[6] + m[13] * n[7]. } void mult(Matrix &ma) { float *n = ma. t[11] = m[3] * n[8] + m[7] * n[9] + m[11] * n[10] + m[15] * n[11]. t[6] = m[2] * n[4] + m[6] * n[5] + m[10] * n[6] + m[14] * n[7]. 252 . t[3] = m[3] * n[0] + m[7] * n[1] + m[11] * n[2] + m[15] * n[3]. m[13] = m[1] * n[12] + m[5] * n[13] + m[9] * n[14] + m[13] * n[15]. y/=length. yz = y*z.0f . xy = x*y.t. float y. n[10] = z*z*oneminusc+c. t[10] = m[2] * n[8] + m[6] * n[9] + m[10] * n[10] + m[14] * n[11]. t[8] = m[0] * n[8] + m[4] * n[9] + m[8] * n[10] + m[12] * n[11]. float n[11]. t[7] = m[3] * n[4] + m[7] * n[5] + m[11] * n[6] + m[15] * n[7]. // n[7] not used n[8] = xz*oneminusc+ys. n[9] = yz*oneminusc-xs. // n[3] not used n[4] = xy*oneminusc-zs. t[0] = m[0] * n[0] + m[4] * n[1] + m[8] * n[2] + m[12] * n[3].ma. float t[12].

i<nn. float z) { /* m[12] to m[15] are not changed by a scale */ m[0] *= x. float D[n2*n]. m[14] = m[2] * x + m[6] * y + m[10] * z + m[14]. m[5] *= y. } // Returns true at success or false if the matrix was singular bool invert(void) { const int n = 4.8*sizeof(float)). m[8] * n[10]. int j. } // perform the reductions for( i = 0. j++ ) { D[i+n*j+nn] = 0. m[2] *= x. m[15] = m[3] * x + m[7] * y + m[11] * z + m[15]. m[10] * n[10]. m[1] *= x. m[11] *= z. float y. m[10] * n[6]. int k. m[9] * n[10]. i++ ) { for( j = 0.0. float beta. m[8] *= z. m[7] *= y. float z) { /* m[0] to m[11] are not changed by a translate */ m[12] = m[0] * x + m[4] * y + m[8] * z + m[12]. } + + + + + + + + + + + m[9] * n[2].t.0. m[9] *= z. const int n2 = 2*n. } D[i+n*i+nn] = 1. m[13] = m[1] * x + m[5] * y + m[9] * z + m[13].t[1]= m[1] * n[0] + m[5] * n[1] t[2]= m[2] * n[0] + m[6] * n[1] t[3]= m[3] * n[0] + m[7] * n[1] t[4]= m[0] * n[4] + m[4] * n[5] t[5]= m[1] * n[4] + m[5] * n[5] t[6]= m[2] * n[4] + m[6] * n[5] t[7]= m[3] * n[4] + m[7] * n[5] m[8]= m[0] * n[8] + m[4] * n[9] m[9]= m[1] * n[8] + m[5] * n[9] m[10]=m[2] * n[8] + m[6] * n[9] m[11]=m[3] * n[8] + m[7] * n[9] memcpy(m. void translate(float x. i < n. m[6] *= y. m[8] * n[6]. i++ ) // For each row { alpha = D[i*n+i].++i) D[i]=m[i]. // init the reduction matrix for( i = 0. m[11] * n[6]. m[10] *= z. m[11] * n[2]. int i. } void scale(float x. float y. // Get diagonal value 253 . m[10] * n[2]. m[4] *= y. i < n. float alpha. j < n. m[3] *= x. m[11] * n[10]. const int nn = n*n. m[9] * n[6]. for (i=0.

j < n2. return os. k++ ) { if( (k-i) != 0 ) { beta = D[k+n*i].h ////////////////////////////////////////////////////////////////////////////// 254 . m[3] = temp = m[6]. m[8]. #endif m[4]. m[4] = temp.y << ". Feb 2002. // A non-singular matrix is one where inv(inv(A)) = A if(!alpha) return false. for( j = i." << v. m[14] = temp. return true. m[12]. m[2] = temp = m[3]." << v. If so the matrix is singular and we will not // invert it. m[13] = temp.sizeof(float)*nn). m[9]. j++ ) { D[i+n*j] /= alpha.// Make sure it is not 0. j++ ) { D[k+n*j] -= beta*D[i+n*j]. } // Update all other rows. k < n.x << ". Vector& v) { os << "(" << v. } void transpose() { float temp. } } } } // Copy result from D to m memcpy(m. // // A 3d float vector class. } Vector. m[12] = temp.h" ostream& operator<<(ostream& os. for( j = 0. // For each column in this row divide though with the diagonal value so // so alpha becomes 1. for( k = 0. m[13].&D[nn]. m[11] } }. m[1] = temp = m[2]. m[8] = temp. Vector.cpp ////////////////////////////////////////////////////////////////////////////// // Tweaked virtual world (C++ version) // by Jacob Marner. = m[14]. temp = m[1]. j < n2.z << ")". m[7] = temp = m[11]. m[9] = temp. m[6] = temp = m[7]. // ////////////////////////////////////////////////////////////////////////////// #include "Vector.

y.x. z(0) {} Vector(const float x.h" <math. y(v. } inline { x -= y -= z -= } void operator-=(const Vector& v) v. y(0).y). } inline Vector operator-(const Vector& v) const { return Vector(x-v.x).h> class Vector { public: float x.z). // // A 3d float vector class. inline void operator*=(const float c) { x *= c. z(z) {} Vector(const Vector& v) : x(v. } inline Vector operator-(void) const { return Vector(-x. const float y. y += v.h> <stddef.z-v.z). Feb 2002. v. return *this. } inline Vector operator*(const float c) const { return Vector(x*c. v.y.y*c.y-v.-y.h" "Matrix.z*c).y. } inline Vector operator/(const float c) const { return Vector(x/c. const float z) : x(x). float z. y(y).y+v.z) {} inline Vector& operator+=(const Vector& v) { x += v.y/c.x.z.x. } inline Vector operator+(const Vector& v) const { return Vector(x+v. Vector(void) : x(0).x.// Tweaked virtual world (C++ version) // by Jacob Marner. z(v.z+v. y *= c.-z). z += v. // ///////////////////////////////////////////////////////////////////////////// #ifndef VECTOR_CLASS #define VECTOR_CLASS #include #include #include #include #include "general.z/c).h> <ostream.y.z. 255 . float y.

z. } inline float getInfinityNorm(void) const { return max(max(Abs(x). return *this. y = -y. inline Vector normalized(void) const { float l = getLength(). } inline Vector inverted(void) const 256 . } // Return a normalized version (unit length) of the // vector. } // Get 2-norm inline float getLength(void) const { return (float)sqrt(x*x+y*y+z*z).z *= c. z = v. c. } inline void normalize(void) { float l = getLength(). c.Abs(z)). return Vector(x / l. } inline { x /= y /= z /= } void operator/=(const float c) c. } inline float getLengthSquared(void) const { return x*x+y*y+z*z.y).y. } // Get 2-norm but before the square root is taken inline float getSquaredLength(void) const { return x*x+y*y+z*z. z /= l. y /= l.z). z = -z. z / l). // Copy vector inline Vector& operator=(const Vector& v) { x = v. x /= l.x. y = v. } inline void invert(void) { x = -x. y / l.Abs(y)). } // Get max of each element (not the same as infinity norm) inline float getMax(void) const { return max(max(x.

Vector& v).x + y * v.y.z.x-x*v.z*v.y && z >= lowerCorner.z. } // Return true if this vector is a position between the lower and upper // corner given of a rectangle. } friend ostream& operator<<(ostream&. The last row of ma is thus not used. z = z1. y = y1. Just throw w away (it is in fact never // calculated. } inline Vector cross(const Vector& v) const { return Vector(y*v.m.-y.{ return Vector(-x. // Do matrix result. but do not divide through with w // aftwards. float x1 = m[0] * x + m[4] * y + m[8] * z + m[12].z-z*v. y = y1.z).m. float x1 = m[0] * x + m[4] * y float y1 = m[1] * x + m[5] * y float z1 = m[2] * x + m[6] * y float w1 = m[3] * x + m[7] * y x = x1. m[9] * z + m[13].x && y < upperCorner. z/=w1.y-y*v. float y1 = m[1] * x + m[5] * y + m[9] * z + m[13].x).y && z < upperCorner. const Vector& upperCorner) const { return (x >= lowerCorner. } } Matrix& ma) + + + + m[8] * z + m[12]. y/=w1. float z1 = m[2] * x + m[6] * y + m[10] * z + m[14]. } inline float dot(const Vector& v) const { return x * v. z = z1. #endif 257 . inline void MultLeftMatrixIgnoreW(const Matrix& ma) { const float* m = ma. inline bool isInRectangle(const Vector& lowerCorner. if (w1!=1) { x/=w1.-z). Each coordinate of lower must be lower // than the same of that of upper. m[11] * z + m[15]. x = x1.z && x < upperCorner.x*v. }.y + z * v. m[10] * z + m[14].x && y >= lowerCorner. } inline void MultLeftMatrix(const { const float* m = ma.

258 .java Matrix. // // This file is used when parsing command line arguments // ////////////////////////////////////////////////////////////////////////////// public class Arguments { String[] args. This class represents a view frustum. i++) { if (args[i]. We also do the scene generation here. The class that handles all the call backs. Feb 2002.Part 2: Java GL4Java source code The source code consists of the following files: Arguments.java gl4java/utils/textures/ BmpTextureLoader.java BoxData. } public boolean getBooleanFlag(String label) { for (int i = 0. i < args. This is similar to the call back functions in GLUT. This is a BMP loader for gl4Java.equals(label)) { return true.args = args. A node in the scene graph / kd-tree.java This file is used when parsing command line arguments This class represents the data generated to represent a box.java EventHandling. It has been submitted for inclusion in later versions of GL4Java. A generic 4x4 matrix float class.java Main.java Arguments. Frustum.java Vector. A generic 3D vector class of floats.java ////////////////////////////////////////////////////////////////////////////// // Tweaked virtual world (GL4Java version) // by Jacob Marner.java Node.length. public Arguments(String[] args) { this. Start this to run the application. The main class. It stores the 6 planes in world coordinates.

////////////////////////////////////////////////////////////////////////////// public class BoxData { public float scale. public Vector position = new Vector().scale.java ////////////////////////////////////////////////////////////////////////////// // Tweaked virtual world (GL4Java version) // by Jacob Marner.assign(b. i++) { if (args[i]. public void assign(BoxData b) { scale = b.startsWith(label)) { String s = args[i].parseInt(s).parseFloat(s).substring(label. i < args. } 259 .assign(b.startsWith(label)) { String s = args[i]. position. } public float getFloatArgument(String label. try { return Float.rotation). float defaultvalue) { for (int i = 0. } } } return defaultvalue. i < args. rotation.substring(label.length()).} } return false. int defaultvalue) { for (int i = 0. // These are converted to 3D geometry when the kd-tree // (scene graph) is built. try { return Integer.length. public Vector rotation = new Vector().position). // // This class represent the data generated to represent // a box. } catch (NumberFormatException e) { return defaultvalue. } } BoxData.length. Feb 2002. } } } return defaultvalue. } public int getIntegerArgument(String label. } catch (NumberFormatException e) { return defaultvalue. i++) { if (args[i].length()).

frustumBottom. EventHandling.0f. public EventHandling() { } Date starttime. private GLUFunc glu. frustumNear. We also do the scene generation here. double double double double double double frustumLeft.util. int[] tex = new int[1]. System.utils. } Random rand.println( "Average frame rate: " + ((float) numFrames) / timeelapsed + " fps. // // The class that handles all the call backs.*. gl4java. frustumTop."). return minValue + offset.textures. } public void init(GLDrawable drawable) 260 .").nextFloat() * interval. float offset = rand.java ////////////////////////////////////////////////////////////////////////////// // Tweaked virtual world (GL4Java version) // by Jacob Marner. Feb 2002. private void startTimer() { starttime = new Date(). gl4java.*.drawable.out.println("Elapsed time: " + timeelapsed + " sec. public float getRandomFloat(float minValue.out. } private void stopTimer() { Date endTime = new Date(). frustumRight. private GLFunc gl. frustumFar = 40. System.minValue. Node root.getTime()) / 1000. float timeelapsed = (endTime.*.}.*.getTime() . // Generate a random number between 0 and maxValue. gl4java. public final static int WORLD_MAX_Y = 100. public final static int WORLD_MAX_Z = 100. // ////////////////////////////////////////////////////////////////////////////// import import import import java. class EventHandling implements GLEventListener { public final static int WORLD_MAX_X = 100.starttime. private GLContext glj. float maxValue) { float interval = maxValue . // This is similar to the call back functions in // GLUT.

gl. glu.0f.glShadeModel(GL_FLAT). GL_NICEST). gl. glj = drawable. GL_MODULATE). texLoader. gl.glFogf(GL_FOG_DENSITY. // Enable fogging gl. gl.glTexParameteri(GL_TEXTURE_2D. glu = drawable.0f. float[] fogColor = { 0.glHint(GL_PERSPECTIVE_CORRECTION_HINT.glClearColor(0. fogColor). 1). // Enable texture mapping gl.0f.getGLContext().glEnable(GL_FOG). tex).glClearColor(0. 261 . (float) (frustumFar * 0. 0.0f }.35f).glEnable(GL_DEPTH_TEST). gl. // Tell OpenGL to use flat shading gl.getTexture()). // Enable Phong lighting model and one global // directional light.0f. gl.readTexture("glow. GL_LINEAR_MIPMAP_LINEAR).0f). tex[0]). 0. gl.glEnable(GL_LIGHT0). glu).exit(1)."). GL_DONT_CARE). gl. gl. texLoader. 0. gl.85f)).getGLComponentFormat().gl = gl. BmpTextureLoader texLoader = new BmpTextureLoader(gl.println("Error loading texture.glEnable(GL_CULL_FACE).getGLFormat().glFogfv(GL_FOG_COLOR. GL_RGB. texLoader. gl.0f. gl. GL_TEXTURE_MIN_FILTER. // This Will Clear The Background Color To Black gl. (float) frustumFar).glEnable(GL_TEXTURE_2D). // Enable back face culling. gl. GL_LINEAR). gl. 0. // Turn on z-buffer for hidden surface removal gl. GL_LINEAR). GL_TEXTURE_ENV_MODE.getImageWidth().isOk()) { System. 0.glCullFace(GL_BACK).getGL(). if (!texLoader.glBindTexture(GL_TEXTURE_2D. Node.glPixelStorei(GL_UNPACK_ALIGNMENT. GL_TEXTURE_MAG_FILTER.gluBuild2DMipmaps( GL_TEXTURE_2D. gl.getImageHeight(). 0.glFogf(GL_FOG_START. 0. // Enable trilinear filtering.glHint(GL_FOG_HINT. 0. texLoader. 0). System. } //Create Texture gl.glEnable(GL_LIGHTING).err.glTexParameteri( GL_TEXTURE_2D.0f.getGLU().glFogf(GL_FOG_END.{ gl = drawable.glFogi(GL_FOG_MODE. texLoader. gl. gl.glGenTextures(1. // Blend lighting a texture effects.glTexEnvf(GL_TEXTURE_ENV.bmp"). 0. texLoader.

Generate each number // randomly.5. gl. GL_SPECULAR. (float) frustumBottom. 262 .y = getRandomFloat(0. 1. (float) frustumRight. 1.glFrustum( frustumLeft. boxData[i]. mat_shininess). frustumLeft = -0. gl. i < numBoxes. gl. height).0f }.5.0f. frustumBottom = -0. 1.rotation. ambient_light). 1. boxData[i].5f). int i. GL_AMBIENT. (float) frustumTop. boxData[i].glLightfv(GL_LIGHT0.glLightfv(GL_LIGHT0. 360.0f. boxData[i]. diffuse_light).0f. pos(3).0f }.5 * ((double) height / (double) width).glLoadIdentity(). mat_specular). float[] diffuse_light = { 1.0f }. 1.x = getRandomFloat(0. frustumNear. 100. frustumNear = 0. 1. int height) { gl. (float) frustumFar). gl. startTimer(). ++i) { boxData[i] = new BoxData().rotation. 100). 0. for (i = 0. GL_SPECULAR.0f.0f. frustumTop.0f.0f. 0. rotation(3).float[] mat_specular = { 1. frustumBottom. numBoxes).glViewport(0. // Generate numBoxes box data put them in array. 360. 0.glMaterialfv(GL_FRONT.0f).x = getRandomFloat(0.rotation.0f. boxData[i]. 1. } public void reshape(GLDrawable d. boxData[i]. frustumFar).0f). 0.position. 1. gl. GL_DIFFUSE. WORLD_MAX_Z).0f. gl. frustumTop = 0.glLightfv(GL_LIGHT0.position. 0.0f.0f.0f.0f. frustumRight = 0.0f). specular_light). 360. float[] mat_shininess = { 50.5.z = getRandomFloat(0. width.y = getRandomFloat(0. WORLD_MAX_X).glMatrixMode(GL_PROJECTION).getTime()).z = getRandomFloat(0.1f.0f.0f. WORLD_MAX_Y). float[] specular_light = { 0. frustumRight.5 * ((double) height / (double) width).glMaterialfv(GL_FRONT. int width. boxData[i].scale = getRandomFloat(0. rand = new Random(new Date(). 1. reshape(drawable. (float) frustumNear. float[] ambient_light = { 1. gl. BoxData[] boxData = new BoxData[numBoxes]. GL_SHININESS.0f. For each one // store scale(1). 1.position. gl. frustum = new Frustum( (float) frustumLeft.0f.0f }. } root = new Node(boxData.0f }.0f.

0f. upVector. 0). float eangle = angle + 3. frustum. Frustum frustum. gl. float[] light_position = { 0.glBegin(GL_QUADS).14f / 2.glMatrixMode(GL_MODELVIEW). cameraPos. public static int numBoxes.0f.z.0f. 0. System. Vector cameraPos = new Vector(). (float) (WORLD_MAX_Z / 2.gluLookAt( cameraPos. cameraDirection. root.assign( (float) (Math. // Move camera in circle around center of world.0f + radius * Math. } float c = 1. Vector cameraDirection = new Vector().z).0f.z.sin(eangle))). glu.glEnd().exit(0).0f. light_position). float angle = (float) frame / 600. 1. WORLD_MAX_Y / 2.glLoadIdentity(). cameraPos.0f }. upVector.sin(angle))). int frame = 0.cos(eangle)).render(frustum).0f.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT). upVector. public static int numFrames. -1. gl.0f. } } public void cleanup(GLDrawable drawable) { } public void preDisplay(GLDrawable drawable) { 263 . public void display(GLDrawable drawable) { //Clear The Screen And The Depth Buffer gl. 1. cameraDirection. gl.z + cameraDirection.y.0f.x. GL_POSITION.set(cameraPos. cameraPos.gl. cameraPos.assign( (float) (WORLD_MAX_X / 2. (float) (Math.y.glLightfv(GL_LIGHT0. Vector upVector = new Vector(0.y. cameraPos. float radius = 20.x. frame++.cos(angle)).y + cameraDirection.x. //Reset The View gl. 0.0f + radius * Math. cameraPos. upVector). if (frame >= numFrames) { stopTimer().x + cameraDirection.

pnear = pnear. public Frustum(float left.top. float pnear.top = top. Vector cameraUp) { // Make sure that all the direction vectors are unit vectors. this. top. this.i++) { points[i] = new Vector().java ////////////////////////////////////////////////////////////////////////////// // Tweaked virtual world (GL4Java version) // by Jacob Marner. But tweaked did in fact increase the speed of this // quite slow class with an order or so. public void set(Vector cameraPos. normals[i] = new Vector(). cameraUp. float pfar) { this.right. pnear.i<6. // // This class represents a view frustum. public Vector center = new Vector().left = left. Vector temp2 = new Vector().normalize().near.bottom. public Vector[] normals = new Vector[6]. cameraDirection. public Vector[] points = new Vector[6].far. It stores the // 6 planes in world coordinates. this. Vector centerOfNearPlane = new Vector(). float bottom. float top.bottom = bottom. Vector cameraDirection. pfar. float right.pfar = pfar. // The first three args is information about the camera in the world.right = right.normalize(). bottom. Vector cameraDirectionCrossedWithUp = new Vector(). 264 . float float float float float float left. // ////////////////////////////////////////////////////////////////////////////// class Frustum { // points and normals for planes in this order: // left. for (int i=0. // The last 6 is the parameters given to glFrustum(). this.} public void postDisplay(GLDrawable drawable) { } } Frustum. right. } } Vector temp = new Vector(). this. // // This class a clear example of how tweaking can reduce the // readbility of Java code. Compare this with the untweaked // version. Feb 2002. float radiusSquared.

// Points for the near anf far planes are found by intersection // viewing direction line with plane. normals[1].added(temp). temp.subtract(cameraPos)). int i.assign(cameraPos). It is now paralell to the near plane.cross(cameraDirection)). temp. for (i=0.multiply(bottom)). // Calculate normal for left side //Vector leftNearPoint = cameraDirection.assign(temp).cross(cameraUp).add(centerOfNearPlane).subtracted(cameraPos). crossed with the upvector (left. cameraDirectionCrossedWithUp.multiply(left) // . // Normal points towards camera normals[5] = cameraDirection. centerOfNearPlane.// Make sure that the up vector is perpendular to the // cameraDirection. We have one point on the plane as the cameraPos. normals[4].add(centerOfNearPlane).assign(cameraDirection).assign(cameraDirection). cameraUp. cameraDirectionCrossedWithUp.i++) points[i] = cameraPos. temp.multiplied(left).crossedWith(cameraUp).added(centerOfNearPlane). cameraUp. points[5]. // Calculate normal for right side //Vector rightNearPoint = cameraDirection.negated(). //normals[1] = (rightNearPoint.multiplied(right).added(centerOfNearPlane). normals[0]. points[5].assign(cameraDirection).assign(cameraUp). 2. temp. // cameraUp = cameraDirection.assign(cameraDirection). temp. //normals[0] = (leftNearPoint. // The following line is a faster version of: // points[5] = cameraPos.crossedWith(temp). // Normal points away from camera // // // // // // // Calculate normals for sides. temp. centerOfNearPlane. temp.add(cameraUp.assign(cameraDirection).subtract(cameraPos)).add(cameraDirection.assign(cameraDirectionCrossedWithUp). // Set normals for near and far planes. temp.subtracted(cameraPos).multiply(right) // .multiplied(pfar).assign(temp). temp.crossedWith(cameraUp).crossedWith(cameraDirection).cross(cameraUp). temp. temp. // Calculate normal for bottom side //Vector bottomNearPoint = centerOfNearPlane.multiply(pfar)).crossedWith(cameraUp). // Calculate center of near plane temp.multiplied(pnear).cross(cameraUp).assign(cameraPos). By moving along the near plane with the given amount we get another point in the plane. temp.assign(cameraDirectionCrossedWithUp).bottom) give us the normal of the plane. temp.right) or the perpendular of the upvector (top. points[4] = centerOfNearPlane.cross(cameraUp. 3. // The camera is in the plane of the sides.cross(cameraUp). temp. normals[4].negate().added(temp).negated(). 265 . The general scheme is: 1. temp. These two points can.i<4. temp.

divided(pnear). temp.subtract(cameraPos)). normals[2]. temp.getSquaredLength().subtract(center). temp2. Default: 3000.subtracted(cameraPos).added(cameraPos).divide(pnear).*.-left)).java ////////////////////////////////////////////////////////////////////////////// // Tweaked virtual world (GL4Java version) // by Jacob Marner. Start this to run the application. // Calculate the center of the frustum //center = points[5].-bottom))) //.multiplied(Math. temp.cross(topNearPoint.assign(centerOfNearPlane).multiplied(bottom).-left))).added(centerOfNearPlane). normals[3].added(temp2).crossedWith(temp). // ////////////////////////////////////////////////////////////////////////////// import java.max(right.negated().subtracted(cameraPos).subtracted(cameraPos). //normals[3] = cameraDirection.assign(cameraDirectionCrossedWithUp). import javax. temp. import java. temp.added(centerOfNearPlane). // Calculate normal for top side //Vector topNearPoint = centerOfNearPlane. temp2. Feb 2002. temp.subtracted(center). } } Main. /*normals[2] = cameraDirection. 266 .-bottom)).cross(cameraUp) . Default: 1000.*. // -boxes:<int> : The number of boxes to generate.multiply(top)). normals[2].divided(2).cross(cameraUp).assign(cameraUp). temp.event.negate(). center.assign(cameraDirectionCrossedWithUp).multiply(Math.added(points[4]).temp. temp.getSquaredLength().*/ normals[2].max(right.add(cameraDirection. normals[3].assign(cameraUp).subtract(cameraPos)).added(temp2).subtract(points[4]). center. temp.assign(cameraUp).add(points[4]).multiply(Math.multiplied(Math. //Vector extremeFarCorner = //extremeNearCorner.crossedWith(temp). temp.multiply(pfar) temp. temp. temp.subtract(cameraPos). temp2.assign(points[5]).swing.assign(cameraDirectionCrossedWithUp). radiusSquared = temp. temp. temp. center.add(cameraUp.awt.divide(2).multiplied(pfar).max(top.subtracted(points[4]).multiplied(top).*. temp2. // // The application takes two optional parameters: // -frames:<int> : The number of frames to run the animation.add(cameraUp.cross(bottomNearPoint.max(top. //radiusSquared = extremeFarCorner.cross(cameraUp) // .awt. // // The main class. //Vector extremeNearCorner = //centerOfNearPlane. center.

start(). GLContext.setVisible(true). canvas). 1000).getIntegerArgument("-frames:". EventHandling.bottom + i.pack().width. f. } public static void main(String args[]) { Main f = new Main("Virtual World"). } public void windowClosing(WindowEvent e) { windowClosed(e). canvas. canvas. GLAnimCanvas canvas = f.right + i. // Create and add the class with the callback methods EventHandling demo = new EventHandling().getContentPane(). f. // Create the 3D canvas Dimension d = f.gljNativeDebug = false. f. // We pack the frame . 267 .otherwise the insets are not // initialized.*.top).exit(0).setUseFpsSleep(false). // Turn of vertical sync and any other pauses.setUseRepaint(false).numBoxes = arg. EventHandling.getInsets(). GLContext. public Main(String s) { super(s). import gl4java.requestFocus().getContentPane(). 0.left. f. 3000). import gl4java. GLContext.*. Arguments arg = new Arguments(args). d.import gl4java.height).canvas = GLDrawableFactory. 640 + i.add("Center".setUseYield(false). canvas.getIntegerArgument("-boxes:".gljClassDebug = false. } }).awt.setBounds(0.gljThreadDebug = false. canvas.getFactory() . canvas.numFrames = arg.setLayout(new BorderLayout()). GLCapabilities caps = new GLCapabilities(). f. public class Main extends JFrame { //Our rendering canvas //We are using GLAnimCanvas because we want the canvas //to be constantly redrawn GLAnimCanvas canvas = null. 480 + i.addWindowListener(new WindowAdapter() { public void windowClosed(WindowEvent e) { System. f.*.getSize(). d. // Set the size of the Window Insets i = f.drawable.addGLEventListener(demo).createGLAnimCanvas(caps. canvas.

m[i]. public Matrix() { } public void loadIdentity() { m[0]=m[5]=m[10]=m[15]=1. m[12] = m12.} } Matrix.i<16. } 268 .float m1.float m9.float m13.float m2. m[2] = m2.float m8. m[9] = m9.float m12. // // All operations are applied such that the // Matrix itself is the Left operand.java ////////////////////////////////////////////////////////////////////////////// // Tweaked virtual world (GL4Java version) // by Jacob Marner. } public Matrix(float m0. m[10] = m10. // // A generic 4x4 matrix float class. m[4] = m4.float m11. m[5] = m5. m[14] = m14. float m10. m[15] = m15.m[i]. } public Matrix(Matrix ma) { for (int i=0. m[7] = m7.i++) m[i] = ma.float m7.i++) m[i] = ma.float m3. Feb 2002. m[13] = m13.float m4.float m6. m[1]=m[2]=m[3]=m[4]=m[6]=m[7]=m[8]=m[9]=m[11]=m[12]=m[13]=m[14]=0. } public void assign(Matrix ma) { for (int i=0. float m15) { m[0] = m0. float m5. // ////////////////////////////////////////////////////////////////////////////// final public class Matrix { public float[] m = new float[16]. m[11] = m11.i<16. m[6] = m6. m[1] = m1. so you can use // glLoadMatrix() on m to import the matrix into // OpenGL.float m14. // // The internal matrix representation is directly // compatible with that of OpenGL. m[3] = m3. m[8] = m8.

m[11] * n[2].0f . zs = z*s. 269 . m[14] = m[2] * n[12] + m[6] * n[13] + m[10] * n[14] + m[14] * n[15]. m[13] = m[1] * n[12] + m[5] * n[13] + m[9] * n[14] + m[13] * n[15]. n[10] = z*z*oneminusc+c. if (slength!=1. t[8] = m[0] * n[8] + m[4] * n[9] + m[8] * n[10] + m[12] * n[11]. float y. m[15] = m[3] * n[12] + m[7] * n[13] + m[11] * n[14] + m[15] * n[15]. t[9] = m[1] * n[8] + m[5] * n[9] + m[9] * n[10] + m[13] * n[11].sqrt(slength). z/=length. float z) { // Normalize axis vector float slength = x*x+y*y+z*z. // n[7] not used n[8] = xz*oneminusc+ys.sin(rad). ys = y*s. m[10] * n[2]. x/=length. m[10] * n[6]. y/=length. m[8] * n[6]. s = (float)Math. n[2] = xz*oneminusc-ys. t[0]= m[0] * n[0] + m[4] * n[1] + t[1]= m[1] * n[0] + m[5] * n[1] + t[2]= m[2] * n[0] + m[6] * n[1] + t[3]= m[3] * n[0] + m[7] * n[1] + t[4]= m[0] * n[4] + m[4] * n[5] + t[5]= m[1] * n[4] + m[5] * n[5] + t[6]= m[2] * n[4] + m[6] * n[5] + t[7]= m[3] * n[4] + m[7] * n[5] + by a rotate */ m[8] * n[2]. t[2] = m[2] * n[0] + m[6] * n[1] + m[10] * n[2] + m[14] * n[3]. m[9] * n[2]. n[1] = xy*oneminusc+zs.i++) m[i]=t[i]. m[11] * n[6]. // n[3] not used n[4] = xy*oneminusc-zs. t[11] = m[3] * n[8] + m[7] * n[9] + m[11] * n[10] + m[15] * n[11].public void mult(Matrix ma) { float[] n = ma. t[0] = m[0] * n[0] + m[4] * n[1] + m[8] * n[2] + m[12] * n[3]. n[0] = x*x*oneminusc+c.m. t[5] = m[1] * n[4] + m[5] * n[5] + m[9] * n[6] + m[13] * n[7]. /* m[12] to m[15] are not changed float[] t = new float[8]. m[9] * n[6]. xy = x*y. n[5] = y*y*oneminusc+c. m[12] = m[0] * n[12] + m[4] * n[13] + m[8] * n[14] + m[12] * n[15]. float float float float float float float float float c = (float)Math. n[6] = yz*oneminusc+xs. n[9] = yz*oneminusc-xs. } float rad = (float)Math. for (int i=0.c. float[] t = new float[12]. } public void rotate(float a. oneminusc = 1. t[10] = m[2] * n[8] + m[6] * n[9] + m[10] * n[10] + m[14] * n[11]. xs = x*s. t[7] = m[3] * n[4] + m[7] * n[5] + m[11] * n[6] + m[15] * n[7].cos(rad). t[3] = m[3] * n[0] + m[7] * n[1] + m[11] * n[2] + m[15] * n[3]. t[4] = m[0] * n[4] + m[4] * n[5] + m[8] * n[6] + m[12] * n[7]. t[1] = m[1] * n[0] + m[5] * n[1] + m[9] * n[2] + m[13] * n[3]. t[6] = m[2] * n[4] + m[6] * n[5] + m[10] * n[6] + m[14] * n[7]. yz = y*z.0f) { float length = (float)Math. float x.i<12. xz = x*z. float[] n = new float[11].toRadians(a).

m[10] *= z.m[8]= m[0] * n[8] + m[4] m[9]= m[1] * n[8] + m[5] m[10]=m[2] * n[8] + m[6] m[11]=m[3] * n[8] + m[7] for (int i=0. m[1] *= x.0f. i < n. // A non-singular matrix is one where inv(inv(A)) = A if(alpha==0. m[6] *= y. float y. // Get diagonal value // Make sure it is not 0. for (i=0.0f. 270 .i<nn. i++ ) { for( j = 0. m[9] *= z. m[8] *= z. m[11] * n[10]. i++ ) // For each row { alpha = D[i*n+i]. public void translate(float x. } // Returns true at success or false if the matrix was singular public boolean invert() { final int n = 4. m[15] = m[3] * x + m[7] * y + m[11] * z + m[15]. m[11] *= z. } // perform the reductions for( i = 0. float z) { /* m[12] to m[15] are not changed by a scale */ m[0] *= x. // init the reduction matrix for( i = 0. m[10] * n[10]. } D[i+n*i+nn] = 1. final int nn = n*n. float z) { /* m[0] to m[11] are not changed by a translate */ m[12] = m[0] * x + m[4] * y + m[8] * z + m[12]. float alpha. m[3] *= x. i < n.i<8. m[2] *= x. j < n. m[14] = m[2] * x + m[6] * y + m[10] * z + m[14]. float y. final int n2 = 2*n.0f) return false. m[7] *= y.++i) D[i]=m[i]. m[9] * n[10]. int i. } public void scale(float x. m[4] *= y. m[5] *= y. float[] D = new float[n2*n]. j++ ) { D[i+n*j+nn] = 0. m[13] = m[1] * x + m[5] * y + m[9] * z + m[13]. int k. If so the matrix is singular and we will not // invert it. int j.i++) m[i]=t[i]. } * * * * n[9] n[9] n[9] n[9] + + + + m[8] * n[10]. float beta.

for( j = 0. for( k = 0. temp = m[7]. j++ ) { D[i+n*j] /= alpha. temp = m[11]. } public void transpose() { float temp. temp = m[2]. m[9] = temp. } } } } // Copy result from D to m for (i=0. k++ ) { if( (k-i) != 0 ) { beta = D[k+n*i]. static final int MAX_BOXES_PER_LEAF = 1. j++ ) { D[k+n*j] -= beta*D[i+n*j]. n is the size of the array.awt. m[4] = temp. m[2] = m[8]. m[1] = m[4]. m[7] = m[13]. int numindex) { boundingBox[0] = 271 . j < n2. } }. Each node contains a display list.*. temp = m[1]. int startindex. Node. m[11] = m[14]. // ////////////////////////////////////////////////////////////////////////////// import import import import java. } // Update all other rows. temp = m[6].i<nn. m[8] = temp. temp = m[3]. gl4java.util. // // Every node has a bounding box and zero or two // children. m[3] = m[12]. Feb 2002. // // A node in the scene graph / kd-tree. j < n2. m[6] = m[9]. return true. gl4java. for( j = i. public Node(BoxData boxData[].*. gl4java. class Node implements GLEnum { public static GLFunc gl.java ////////////////////////////////////////////////////////////////////////////// // Tweaked virtual world (GL4Java version) // by Jacob Marner. m[12] = temp. // This method may reorder the ordering of the // box data array.// For each column in this row divide though with the diagonal value so // so alpha becomes 1.*. m[14] = temp.GLContext. m[13] = temp. k < n.i++) m[i] = D[i+nn].

boxMaxDistFromCenter. if (candidate > boundingBox[1]. candidate = boxData[c].position.0f. 0). float yspan = boundingBox[1]. startindex. startindex.z + boxMaxDistFromCenter. center. candidate = boxData[c]. if (candidate > boundingBox[1].x + xspan / 2. if (candidate < boundingBox[0]. 2)). } boxCenter = boundingBox[1]. boxRadiusSquared = boxCenter.0f. // Reorder box data according to center on the max axis.position. } else { if (yspan >= zspan) { // Find center along max axis float center = boundingBox[0].WORLD_MAX_Z).y) boundingBox[1]. float zspan = boundingBox[1].divide(2).boxMaxDistFromCenter. EventHandling.0f * (float) Math. float candidate. candidate = boxData[c].x = candidate. ++c) { float boxMaxDistFromCenter = (float) Math.x . if (candidate < boundingBox[0]. splitn = reorder(boxData.scale.2)) ) for (int c = startindex.subtract(boundingBox[0]).position.subtract(boundingBox[0]). if (candidate > boundingBox[1].z) boundingBox[1]. if (candidate < boundingBox[0]. splitn = reorder(boxData. numindex.z = candidate.y + yspan / 2.boundingBox[0]. boundingBox[1] = new Vector(0.y . candidate = boxData[c].sqrt(2.z .x) boundingBox[0]. 0).y . // Reorder box data according to center on the max axis. EventHandling.position.y. numindex. // Find and set bounding box by iterating through boxes.y = candidate. 1). 272 .y + boxMaxDistFromCenter.boundingBox[0].pow(boxData[c].getSquaredLength().position.boundingBox[0]. if (xspan >= yspan && xspan >= zspan) { // Find center along max axis float center = boundingBox[0].z) boundingBox[0].z.y = candidate.boxMaxDistFromCenter.WORLD_MAX_X.x. c < startindex + numindex. 0.x = candidate. center. candidate = boxData[c].x) boundingBox[1]. candidate = boxData[c].x + boxMaxDistFromCenter.add(boundingBox[0]).z .y) boundingBox[0].x . // (Boxes is centered about position and has a distance // from center of max sqrt(2*pow(scale.WORLD_MAX_Y.z = candidate. if (numindex > 2) { float xspan = boundingBox[1].new Vector( EventHandling.position. // If n is more than max_boxes if (numindex > MAX_BOXES_PER_LEAF) { int splitn.

splitn . 1.z + zspan / 2.} else { // Find center along max axis float center = boundingBox[0]. } if (splitn == startindex) splitn = startindex + 1.1. if (displayList == 0) { System.glCallList(rightChild. gl.rotate(boxData[i]. startindex + numindex .rotate(boxData[i].glEndList(). 273 .err. Display used before set.translate( boxData[i]. } gl.0f.").invert()) + numindex. splitn. This display list // must be executed within a begin() end() block that was started // with GL_QUAD_STRIP displayList = gl.x. m. Display used before set. if (displayList == 0) { System. 2). numindex. i < startindex { Matrix m = new Matrix(). rightChild = new Node(boxData.glNewList(displayList. 1). boxData[i]. gl. 0). System. scale). if (!t.z). gl. boxData[i].displayList).position.y.loadIdentity(). ++i) 0). m. Matrix t = new Matrix(m). 0.rotation. 0.glNewList(displayList.rotation.scale. splitn = reorder(boxData.exit(1). GL_COMPILE). for (int i = startindex. 0. startindex.x. if (splitn == startindex + numindex) splitn = startindex + numindex .splitn). GL_COMPILE). } gl. System.z. m.glGenLists(1).y.exit(1).scale(scale.println("Internal error.glGenLists(1). // Initialize display list that calls children display list displayList = gl. scale. // Create nodes for each child leftChild = new Node(boxData.displayList).glCallList(leftChild. m.rotation. // Reorder box data according to center on the max axis.println("Internal error. 1. 0.err. center. float scale = boxData[i]."). } } } else { splitn = startindex + 1.startindex). m. m.position. startindex.rotate(boxData[i].position. } else { // Initialize display list to draw the boxes.

consisting of 1. 1. 0. -1. Bounding spheres for the frustum and bounding box is precalculated. -1). 1. -1. 1. 1. -1. 1. 1. transformAndDrawVertex(m. -1). transformAndDrawVertex(m. 0. 0). -1. transformAndDrawVertex(m. 1). transformAndDrawVertex(m. System. 1. -1. -1). 1. 0. -1. 1). 1. 1.glEndList(). 1. 1. // Top side transformAndSetNormal(t.println("Internal error. -1). transformAndDrawVertex(m. -1. 0. 0. 1. Plane to point comparison to see of the box is entirely outside the plane. 0. -1.err. // // // // // // // We do a frustum culling algorithm. 0. -1. -1. Matrix invert failed. 0. 1. 1). 1. -1. } t. 1. 1. -1. 2. -1). // Under side transformAndSetNormal(t. 1. 0. 1. transformAndDrawVertex(m. 1). -1. transformAndDrawVertex(m. 1. } } protected void finalize() { gl. 1. // Front side transformAndSetNormal(t. -1. 0. -1. 1). 0. 0."). 1. 1. -1). These are used to do a bounding sphere culling. 0. 1. 0. 1. 1. } gl. transformAndDrawVertex(m. -1). 1. 1. transformAndDrawVertex(m. -1. -1). 274 . transformAndDrawVertex(m. 1. // Right side transformAndSetNormal(t. 0. 0. transformAndDrawVertex(m. -1). 1. -1. transformAndDrawVertex(m. transformAndDrawVertex(m. -1. 1. transformAndDrawVertex(m. 1. 0). -1. transformAndDrawVertex(m. 1. transformAndDrawVertex(m. 0.transpose(). 1. 0. 0. 0. 1). 0. -1). We only test to points per bounding box for each plane. 0. 0). 1.glDeleteLists(displayList. -1). transformAndDrawVertex(m.exit(1). 0. transformAndDrawVertex(m. 1. 1. 1. -1. 1). // Back side transformAndSetNormal(t. 1). 0. 1. transformAndDrawVertex(m. transformAndDrawVertex(m. 1). transformAndDrawVertex(m. 0. 1. boolean isInside. // Left side transformAndSetNormal(t. transformAndDrawVertex(m. -1. 1). 1). 1. -1). 0. static Vector temp = new Vector(). transformAndDrawVertex(m. -1. transformAndDrawVertex(m. 1. 1. -1. -1). 0. 1. -1. 0. } // First min box then max box Vector[] boundingBox = new Vector[2]. 1). 0. 0. 0. 1. 1). 1.{ System. -1. 1). 1. 1. 0). 0. -1. 1. 1.

if (basicCullTest(plane. // If it is a leaf draw it all.glTexCoord2f(texx. temp. } } // Is the box fully within the frustum? If yes. } else { leftChild.render(frustum).center). int lastPlaneCulled = 0.render(frustum). return. float posx.assign(boxCenter). plane < 6. frustum)) return. private void transformAndDrawVertex( Matrix m. temp. float posy. posy. float texx.MultLeftMatrix(m).radiusSquared + boxRadiusSquared) return. Node leftChild = null.getSquaredLength() > frustum. if not go to children nodes. frustum)) { lastPlaneCulled = plane. if (leftChild == null) { gl. if (basicCullTest(lastPlaneCulled. return. public void render(Frustum frustum) { isInside = true. float texy.glCallList(displayList). // Note: We do not use the octant test to reduce the plane // comparisons needed to 3 since that requires a symmetric // frustum. rightChild. 275 . int plane. } } int displayList.glCallList(displayList). w.// 3. Temporal coherance culling. simply render the // display list. float posz) { gl. We remember what plane // was used to cull this the last time and we start with // that. } // We are now in doubt whether the box should be drawn.subtracted(frustum. texy). Vector w = new Vector(posx. if (temp. posz). Node rightChild = null. if (isInside) { gl. plane++) { if (plane == lastPlaneCulled) continue. for (plane = 0.

nz]. if (isOutside(boundingBox[nx]. z). return normal. n)) { // Check if corner closest to plane in normal's direction is outside. 1 gives y.subtracted(point). v. } private void transformAndSetNormal(Matrix t.y.z.x.MultLeftMatrixIgnoreW(t). int offset) { BoxData temp = new BoxData(). boundingBox[1 .normalize(). } static private boolean isOutside( float x. v.x. float z. gl. v.x >= 0 ? 1 : 0.z >= 0 ? 1 : 0. float y.glNormal3f(v. int numindex.z). the box is intersecting the fustrum border. Vector point. } return false. static int reorder( BoxData boxData[]. // If not it is fully inside. float y. 276 . the box is fully outside.dot(temp) >= 0.normals[plane]. z). float splitvalue. float boxRadiusSquared.points[plane].x.assign(x.points[plane]. Vector normal) { temp. int nx = n.glVertex3f(w.nx]. y.y. frustum.x. temp. // If no. // If yes.y >= 0 ? 1 : 0.z). if (isOutside(boundingBox[1 . frustum. // Check if corner furthest from plane in normals direction is outside. boundingBox[ny]. n)) { return true.gl. Frustum frustum) { // Find corner furthest from plane in normals direction. If offset is 0 then // x is used. boundingBox[1 . int nz = n. float x. v. int ny = n. w. } Vector boxCenter.y. } else isInside = false. 2 gives z.ny]. } private boolean basicCullTest(int plane. // Reorder boxData array according to value at given offset. y. Vector n = frustum.z. boundingBox[nz]. w. int startindex. float z) { Vector v = new Vector(x.y.

public Vector() { x = 0.x = x.position. public float y.index(offset) > splitvalue) j--. while (j >= p && boxData[j].z = z. int i = p . // // A generic 3D vector class of floats. boxData[j]. } public Vector(final Vector v) 277 .1. y = 0. // Note that objects of this class are mutable.assign(temp).index(offset) < splitvalue) i++. int p = startindex. j--.assign(boxData[j]). int r = startindex + numindex .position. while (true) { i++. public float z. } public Vector(final float x.index(offset) < splitvalue) j++. if (i < j) { temp.position.y = y. this. boxData[i]. if (j <= p) j = p. Vector.assign(boxData[i]). z = 0. final float z) { this. } } } }. final float y. while (i <= r && boxData[i]. // ////////////////////////////////////////////////////////////////////////////// final public class Vector { public float x. int j = r + 1. return j. Feb 2002. this.java ////////////////////////////////////////////////////////////////////////////// // Tweaked virtual world (GL4Java version) // by Jacob Marner.if (numindex < 1) return 0. } else { while (j < r && boxData[j].1.

z / c).z. y = v. y /= c.y. y / c.y.v. -z. y += v. y -= v. } final { x = y = z = } public void negated() -x. z *= c. } final public void added(final Vector v) { x += v. -y.x.y.y. } final public Vector subtract(final Vector v) { return new Vector(x . z = v.z). z . y *= c.v. -z).x.{ x = v. } final public Vector negate() { return new Vector(-x. } final public Vector add(final Vector v) { return new Vector(x + v. y * c. } final public void subtracted(final Vector v) { x -= v.x. y = v. } // Copy vector final public void assign(final Vector v) { x = v.x. y .v. z /= c.y.x.z). } final public Vector divide(float c) { return new Vector(x / c. z -= v. -y. z += v.z. } final public void multiplied(final float c) { x *= c. 278 .y.x. } final public void divided(final float c) { x /= c. final public Vector multiply(final float c) { return new Vector(x * c. z + v. z * c).z. y + v.

final float ty = z * v. 279 .y .y.y * v.y . return new Vector(x / l. final float y. -y.x . y). } final public void assign(final float x.x).abs(x). } final public Vector cross(final Vector v) { return new Vector(y * v.z * v.y.sqrt(x * x + y * y + z * z).max(Math.y * v. } final { x = y = z = } public void invert() -x. final float z) { this. z).x * v. x /= l. this.x .z . } final public void normalize() { final float l = getLength().x * v.x = x. final public Vector inverted() { return new Vector(-x. z = x * v.z = v. z * v.abs(z)). y / l. Math. z / l).x.z. this.z.max(Math.z = z. final public Vector normalized() { final float l = getLength().z . } // Return a normalized version (unit length) of the // vector.y = y. } final public void crossedWith(final Vector v) { final float tx = y * v. } // Get 2-norm but before the square root is taken final public float getSquaredLength() { return x * x + y * y + z * z. -y. z /= l.z. y /= l.max(x.max(Math. } final public float getInfinityNorm() { return Math. -z. } // Get 2-norm final public float getLength() { return (float) Math.z * v. Math. -z). x * v.abs(y)). } // Get max of each element (not the same as infinity norm) final public float getMax() { return Math.

} } final public float dot(final Vector v) { return x * v.z). Just throw w away (it is in fact never // calculated.z. y /= w1. final public void MultLeftMatrixIgnoreW(final Matrix ma) { final float[] m = ma.z && x < upperCorner. but do not divide through with w // afterwards.y && z < upperCorner.y + z * v. final Vector upperCorner) { return ( x >= lowerCorner. y on 1 and z on 2. Each coordinate of lower must be lower // than the same of that of upper. z /= w1.y && z >= lowerCorner. z = z1. final float x1 = m[0] * x + m[4] * y final float y1 = m[1] * x + m[5] * y final float z1 = m[2] * x + m[6] * y final float w1 = m[3] * x + m[7] * y x = x1. m[9] * z + m[13]. if (w1 != 1) { x /= w1. x = x1. case 1 : return y. y = y1. m[11] * z + m[15].x && y < upperCorner.x + y * v. The last row of ma is thus not used. z = z1. } } // Do matrix result.x = tx. y = ty. } // Return x on 0. final float z1 = m[2] * x + m[6] * y + m[10] * z + m[14]. } // Return true if this vector is a position between the lower and upper // corner given of a rectangle. final public float index(final int n) { switch (n) { case 0 : return x. y = y1. final public boolean isInRectangle( final Vector lowerCorner. final float y1 = m[1] * x + m[5] * y + m[9] * z + m[13]. } final public void MultLeftMatrix(final { final float[] m = ma. m[10] * z + m[14]. 280 . Matrix ma) + + + + m[8] * z + m[12].m. final float x1 = m[0] * x + m[4] * y + m[8] * z + m[12]. default : throw new ArrayIndexOutOfBoundsException().m. case 2 : return z.x && y >= lowerCorner.

import import import import import import import java. This loader implements the * full .} }. return true. if (res!=SUCCESS) { error=true.*. glFormat = GL_RGB. java.*. * * Before using the loaded texture data you * should call * glPixelStorei(GL_UNPACK_ALIGNMENT. bitmapData). java.*. java.*. import gl4java. * * The Windows BMP format does not support * multiple layers or transparency.image.applet. java. imageHeight = height. pixel = bitmapData[0].awt. return false.Color. int res = loadBMP(is. 4-bit. gl4java/utils/textures package gl4java.net. setTextureSize().*. 281 . * This loader supports all the variants.awt.*. java.*. * * @author Jacob Marner jacob@marner. } // The operation ended successfully private final static int SUCCESS = 0.BMP * texture loader. 1). } protected boolean readTexture(InputStream is) { byte[][] bitmapData = new byte[1][]. * The 4-bit and 8-bit mode can be RLE encoded.io. GLUFunc glu) { super(gl.BMP specification. java. glu). 8-bit and 24-bit colors.textures.*. } imageWidth = width.awt. It supports * 1-bit.event.awt. /** * This class implements a Window .dk * @see IOTextureLoader * @see TextureLoader */ public class BmpTextureLoader extends IOTextureLoader { public BmpTextureLoader(GLFunc gl. * The rest of the PixelStore * arguments should stay at the default.utils.

// Compression private final static int BI_RGB = 0. // The number of bytes read so far private int bytesRead. Return value: [out] Status of operation. // Palette used for paletted images during load. private InputStream file.if it is not there it is appended automatically. // The offset from the BITMAPFILEHEADER structure // to the actual bitmap data in the file. // The bitmap will be loaded without borders and all mip maps will be // generated automatically. If you need the bitmap for anything else than // textures. For the filename string given as parameter the extension is optional . Is 1. private int read32BitValue() { 282 . // Number of bit per pixel. // Image height in pixels private int height. Defaults to GL_RGB. private final static int BI_RLE4 = 2. If you later want to delete the loaded texture call glDeleteTextures() on the returned texture name.// The file could not be found or it could not be opened for reading. // The file does not comply with the specification. private int bitCount. You proably used a internal // format not accepted by your OpenGL implementation or you may have run // out of available texture names. // Image width in pixels private int width. 4. The status of the operation will be returned as the return value. If an error occured. private static int compression.BMP format. [in/out] textureName: Pass a pointer to an already allocated GLuint and if the operation success then this data will point to the texture name of the texture object loaded. private byte[][] palette = new byte[3][256]. If it is 1. private final static int BI_RLE8 = 1. or you need borders you will have to edit the source code. texturename is undefined. whose name will be returned in textureName. private final static int OPENGL_ERROR = 3.4 or 8 then // images is paletted. 24. If the return value is not LOAD_TEXTUREBMP_SUCCESS then this value is undefined. This can immediately be used with glBindTexture() to use the texture during drawing. // // // // Loads the specified BMP image and stores it into a OpenGL 2D texture. [in] internalformat: The internal format of the loaded texture. // The system ran out of heap memory during the texture load. private final static int ILLEGAL_FILE_FORMAT = 2. private int byteOffset. // Reads and returns a 32-bit value from the file. // // // // // // // // // // // // // // // // Parameters: [in] filename: The name of the texture file.bmp extension. private final static int COULD_NOT_FIND_OR_READ_FILE = 1. Must be Windows . Is passed directly to glTextImage2D(). private final static int OUT_OF_MEMORY = 4. 8. // OpenGL could not accept the texture. The actual file *must* use the . othewise not.

// UINT bfReserved1.bytesRead += 4. return c1 + (c2 << 8) + (c3 << 16) + (c4 << 24).read().read(). } } // Reads and returns a 16-bit value from the file private short read16BitValue() { bytesRead += 2. file. try { int int int int c1 c2 c3 c4 = = = = file. // Read file size.read(). return c1.read(). } } // In Windows terms read this structure // typedef struct tagBITMAPFILEHEADER { /* bmfh */ // UINT bfType. // DWORD bfOffBits. file. file. read32BitValue(). // DWORD bfSize. // UINT bfReserved2.read().read(). } catch(IOException e) { return 0. int c2 = file. try { int c1 = file. // } BITMAPFILEHEADER.read(). Ignore. private int readFileHeader() { // Read "BM" tag in ASCII. We do not need this. // Skip the two reserved areas read16BitValue(). 283 . } catch(IOException e) { return 0. if (read8BitValue() != 66 || read8BitValue() != 77) return ILLEGAL_FILE_FORMAT. return (short)(c1 + (c2 << 8)). } catch (IOException e) { return 0. try { int c1 = file. } } // Reads and returns a 8-bit value from the file (0-255) private int read8BitValue() { bytesRead += 1.

if (compression != BI_RGB && compression != BI_RLE8 && compression != BI_RLE4) return ILLEGAL_FILE_FORMAT. } // In windows terms read this struct: //typedef struct tagBITMAPINFOHEADER { /* bmih */ // DWORD biSize. if (sizeOfInfoHeader < 40) return ILLEGAL_FILE_FORMAT. // Read colors used. int sizeOfInfoHeader = (int) read32BitValue(). // Read the byte offset byteOffset = read32BitValue(). // LONG biYPelsPerMeter. // LONG biWidth. if (bitCount != 1 && bitCount != 4 && bitCount != 8 && bitCount != 24) return ILLEGAL_FILE_FORMAT. We do not need this. // WORD biPlanes. read32BitValue(). // Pixel to device mapping. // DWORD biCompression. We do not need this since we have the // image size. width = read32BitValue(). // DWORD biClrImportant. // LONG biXPelsPerMeter. // Read the number of important colors. read32BitValue(). If it is larger we read // some more bytes in the end to be forward compatible. height = read32BitValue(). sizeOfInfoHeader -= 40. read32BitValue().read16BitValue(). // LONG biHeight. This will not be needed // in OpenGL so we will ignore this. According to the specification this // must be 1. read32BitValue(). // Read number of planes. 284 . This is irrelevant since we simply // want the bitmap. return SUCCESS. // WORD biBitCount. if (read16BitValue() != 1) return ILLEGAL_FILE_FORMAT. // DWORD biSizeImage. bitCount = read16BitValue(). // Read image size. private int readInfoHeader() { // We expect this to be at least 40. read32BitValue(). // DWORD biClrUsed. // Apply padding in end of header to be forward compatible. //} BITMAPINFOHEADER. so it is ignored. compression = read32BitValue(). while (sizeOfInfoHeader > 0) { read8BitValue().

private int readPalette() { // 24-bit images are not paletted.((char)(x % 8)))) & 1). bitmapData[index + x * 3] = palette[0][color]. i++) j >= 0. while (bytesRead % 4 != 0) read8BitValue(). } // This is called after the first byte has been found to be 0 // and the second to be 0-2. < numColors. i { // Read RGB. // BYTE rgbGreen. This is only used in RLE-4 and RLE-8 // encoding. j--) = (byte)read8BitValue(). } return SUCCESS. int byteRead = 0. for (int x = 0. private boolean handleEscapeCode( 285 . } // The palette follows directly after the // info header // //typedef struct tagRGBQUAD { /* rgbq */ // BYTE rgbBlue. int numColors = 1 for (int i = 0. y < height. // BYTE rgbReserved.sizeOfInfoHeader--. // Skip reversed byte. } // Pad to 32-bit boundery. } return SUCCESS. // BYTE rgbRed. if (bitCount == 24) return SUCCESS. } char color = (char)((byteRead >> (7 . bitmapData[index + x * 3 + 2] = palette[2][color]. } return SUCCESS. x++) { if (x % 8 == 0) { byteRead = read8BitValue(). } private int readBitmapData1Bit(byte[] bitmapData) { // 1-bit format cannot be compressed if (compression != BI_RGB) return ILLEGAL_FILE_FORMAT. x < width. // } RGBQUAD. read8BitValue(). for (int y = 0. palette[j][i] << bitCount. bitmapData[index + x * 3 + 1] = palette[1][color]. for (int j = 2. y++) { int index = y * width * 3.

} else if (secondByte == 0x01) { // End of bitmap res[0] = SUCCESS. } bitmapData[index + i * 3] = palette[0][color]. int[] res) { if (secondByte == 0x00) { // End of line x[0] = 0. int[] y. int color. for (int i = 0. if (x[0] >= width || x[0] < 0 || y[0] >= height || y[0] < 0) { res[0] = ILLEGAL_FILE_FORMAT. } // Pad to 32-bit boundery. if (y[0] >= height) { res[0] = ILLEGAL_FILE_FORMAT. return true. 286 . x[0] += read8BitValue(). i++) { if (i % 2 == 0) { byteValue = read8BitValue(). } (y[0]) ++. private int readBitmapData4Bit(byte[] bitmapData) { if (compression != BI_RGB && compression != BI_RLE4) return ILLEGAL_FILE_FORMAT. Can be uncompressed or RLE-4 compressed. } } return false. int byteValue = 0. bitmapData[index + i * 3 + 2] = palette[2][color]. int[] x.int secondByte. i < width. bitmapData[index + i * 3 + 1] = palette[1][color]. for (int k = 0. y[0] += read8BitValue(). Move drawing cursor. j < height. j++) { int index = j * width * 3. return true. return true. } else { color = (byte)(byteValue & 0x0F). k++) read8BitValue(). } // Draw a 4-bit image. } else // secondByte=0x02 { // Delta. // Uncompressed 4 bit encoding if (compression == BI_RGB) { for (int j = 0. color = (byte)(byteValue >> 4). k < (((width + 1) / 2) % 4).

for (int i = 0. } else { // Absolute encoding int index = y[0] * width * 3. } else { color = databyte & 0x0F. int color. x[0] = 0. y[0] = 0. 287 . } // Pad to 16-bit word boundery while (bytesRead % 2 != 0) read8BitValue(). i < secondByte. for (int i=0. } bitmapData[index + x[0] * 3] = palette[0][color]. // Clear bitmap data since it is legal not to // fill it all out. (x[0])++. bitmapData[index + x[0] * 3 + 2] = palette[2][color]. if (i % 2 == 0) { databyte = read8BitValue(). // Is this an escape code or absolute encoding? if (firstByte == 0) { // Is this an escape code if (secondByte <= 0x02) { int[] res = new int[1]. x. color = databyte >> 4. bytesRead = 0. perform data decode for next // length of colors int color1 = secondByte >> 4.} } // RLE encoded 4-bit compression if (compression == BI_RLE4) { // Drawing cursor pos int[] x = new int[1]. i++) { if (x[0] >= width) return ILLEGAL_FILE_FORMAT. int databyte = 0. bitmapData[index + x[0] * 3 + 1] = palette[1][color]. y.i<width*height*3. res)) return res[0]. int[] y = new int[1]. int secondByte = read8BitValue().i++) bitmapData[i] = 0. if (handleEscapeCode(secondByte. } } else { // If not absolute or escape code. while (true) { int firstByte = read8BitValue().

private int readBitmapData8Bit(byte[] bitmapData) { if (compression != BI_RGB && compression != BI_RLE8) return ILLEGAL_FILE_FORMAT. } // go to next alignment of 4 bytes. i < height. y[0] = 0. j < width. bitmapData[index + j * 3 + 2] = palette[2][color].i++) bitmapData[i] = 0. k++) read8BitValue(). for (int j = 0. while (true) { int firstByte = read8BitValue(). if (compression == BI_RGB) { // For each scan line for (int i = 0.i<width*height*3. int index = y[0] * width * 3 + x[0] * 3. i++) { int index = i * width * 3. if (i % 2 == 0) color = color1. } } if (compression == BI_RLE8) { // Drawing cursor pos int[] x = new int[1]. bitmapData[index + j * 3] = palette[0][color]. x[0] = 0. bitmapData[index + 2] = palette[2][color]. bitmapData[index + 1] = palette[1][color]. } // Read 8-bit image. int secondByte = read8BitValue(). j++) { int color = read8BitValue(). } } } } return SUCCESS. i < firstByte. int color. for (int i=0. i++) { if (x[0] >= width) return ILLEGAL_FILE_FORMAT.int color2 = secondByte & 0x0F. bitmapData[index + j * 3 + 1] = palette[1][color]. bytesRead = 0. Can be uncompressed or RLE-8 compressed. bitmapData[index] = palette[0][color]. int[] y = new int[1]. for (int i = 0. (x[0])++. k < (width % 4). 288 . for (int k = 0. else color = color2. // Clear bitmap data since it is legal not to // fill it all out.

if (compression != BI_RGB) return ILLEGAL_FILE_FORMAT. y. for (int i = 0. bitmapData[index + 2] = palette[2][secondByte]. k++) read8BitValue(). } } else { // If not. for (int k = 0. } else { // Absolute encoding int index = y[0] * width * 3. bitmapData[index + 1] = palette[1][secondByte]. } // Load a 24-bit image. } } } } return SUCCESS. if (handleEscapeCode(secondByte. i < firstByte. i < secondByte. } // Pad to 16-bit word boundery if (secondByte % 2 == 1) read8BitValue(). int index = y[0] * width * 3 + x[0] * 3. bitmapData[index + j * 3 + 1] = (byte)read8BitValue(). x. int color = read8BitValue(). bitmapData[index + x[0] * 3] = palette[0][color]. res)) return res[0]. bitmapData[index + x[0] * 3 + 2] = palette[2][color]. (x[0])++. i++) { if (x[0] >= width) return ILLEGAL_FILE_FORMAT. for (int i = 0. bitmapData[index + j * 3] = (byte)read8BitValue(). j < width.// Is this an escape code or absolute encoding? if (firstByte == 0) { // Is this an escape code if (secondByte <= 0x02) { int[] res = new int[1]. Verify this. i < height. private int readBitmapData24Bit(byte[] bitmapData) { // 24-bit bitmaps cannot be encoded. bitmapData[index] = palette[0][secondByte]. } // go to next alignment of 4 bytes. bitmapData[index + x[0] * 3 + 1] = palette[1][color]. (x[0])++. i++) { int index = i * width * 3. 289 . k < ((width * 3) % 4). j++) { bitmapData[index + j * 3 + 2] = (byte)read8BitValue(). for (int j = 0. Cannot be encoded. i++) { if (x[0] >= width) return ILLEGAL_FILE_FORMAT. perform data decode for next length of colors for (int i = 0.

case 4 : return readBitmapData4Bit(bitmapData). // Read File Header if (res==0) res = readFileHeader(). bitmapData[0] = null. } } // Load the BMP. byte[][] bitmapData) { file = filep. // If successful *bitmapData will contain a pointer to the data. Most images will need no padding // since they already are made to use as little space as // possible. return readBitmapData24Bit(bitmapData). switch (bitCount) { case 1 : return readBitmapData1Bit(bitmapData). private int loadBMP( InputStream filep. // Read Palette if (res==0) res = readPalette(). default : // 24 bit. if (res==0) { // The bitmap data we are going to hand to OpenGL bitmapData[0] = new byte[width * height * 3]. case 8 : return readBitmapData8Bit(bitmapData). } return res. int res = SUCCESS. bytesRead = 0. // Open file for buffered read. if (bitmapData[0]==null) res = OUT_OF_MEMORY.} return SUCCESS. } 290 . while (bytesRead < byteOffset) read8BitValue(). } if (res==0) { // Read Data res = readBitmapData(bitmapData[0]). // The data reading procedure depends on the bit depth. Assumes that no extension is appended to the filename. } private int readBitmapData(byte[] bitmapData) { // Pad until byteoffset. // Read Info Header if (res==0) res = readInfoHeader().

} 291 .

java BoxData. The class derived from Canvas3D. Arguments.length()).i++) { if (args[i].java VisualBox.length. } public int getIntegerArgument(String label.length.java Main. public Arguments(String[] args) { this.args=args. } catch(NumberFormatException e) { 292 . The main class. } public boolean getBooleanFlag(String label) { for(int i=0. Feb 2002.i<args. // // This file is used when parsing command line arguments // ////////////////////////////////////////////////////////////////////////////// public class Arguments { String[] args.startsWith(label)) { String s = args[i].java ////////////////////////////////////////////////////////////////////////////// // Virtual World (Java3D version) // by Jacob Marner.parseInt(s).java MyCanvas3D.i++) { if (args[i]. Start this to run the application.i<args. } } return false.substring(label. try { return Integer.Part 3: Java Java3D source code The source code consists of the following files: Arguments.java This file is used when parsing command line arguments This class represents the data generated to represent a box.equals(label)) { return true. A Shape3D used to draw a box. int defaultvalue) { for(int i=0.

scale. // These are converted to 3D geometry when the kd-tree // (scene graph) is built. // // The main class.rotation). } } } return defaultvalue. Main.startsWith(label)) { String s = args[i].java ////////////////////////////////////////////////////////////////////////////// // Virtual World (Java3D version) // by Jacob Marner.set(b. try { return Float. // // This class represent the data generated to represent // a box. Default: 3000. } } } return defaultvalue. public void assign(BoxData b) { scale = b.substring(label. } catch(NumberFormatException e) { return defaultvalue. } }.position).java ////////////////////////////////////////////////////////////////////////////// // Virtual World (Java3D version) // by Jacob Marner.return defaultvalue.*. public Vector3f position = new Vector3f(). Feb 2002. public Vector3f rotation = new Vector3f(). // -boxes:<int> : The number of boxes to generate. Feb 2002. Start this to run the application. // ////////////////////////////////////////////////////////////////////////////// import javax. Default: 1000. } public float getFloatArgument(String label. // 293 .length.parseFloat(s).set(b. float defaultvalue) { for(int i=0.length()).vecmath. // // The application takes two optional parameters: // -frames:<int> : The number of frames to run the animation.i<args. } } BoxData. position.i++) { if (args[i]. public class BoxData { public float scale. rotation.

getPreferredConfiguration(). int width = 640.vecmath.createSceneGraph(). // Set the size of the Window getContentPane().setCapability(TransformGroup. } public void createUniverse(BranchGroup scene. viewBranch. // Create viewing branch BranchGroup viewBranch = new BranchGroup().media.swing. BranchGroup scene = canvas3D.j3d.sun. public class Main extends JFrame { static Main frame.awt. MyCanvas3D canvas3D. viewTransformGroup = new TransformGroup().getIntegerArgument("-boxes:".top).add("Center".addChild(viewTransformGroup).bottom + i. ViewPlatform viewPlatform = new ViewPlatform(). viewTransformGroup.addChild(viewPlatform). width + i. createUniverse(scene. getContentPane(). Locale locale = new Locale(universe). javax. com. canvas3D).addCanvas3D(canvas3D). 294 .*.setLayout(new BorderLayout()). view.getIntegerArgument("-frames:". 3000). // Create Window frame = new Main("Virtual World Java3D"). canvas3D). // Initialize GraphicsConfiguration config = SimpleUniverse. 1000). MyCanvas3D.*. height + i.right + i.universe.attachViewPlatform(viewPlatform). int height = 480.*. static TransformGroup viewTransformGroup.EXIT_ON_CLOSE).j3d. setBounds(0. Insets i = getInsets(). setVisible(true).numBoxes = arg. javax. view.*. viewTransformGroup. Canvas3D canvas3D) { VirtualUniverse universe = new VirtualUniverse(). setDefaultCloseOperation(JFrame.left.////////////////////////////////////////////////////////////////////////////// import import import import import java. javax. 0. MyCanvas3D.ALLOW_TRANSFORM_WRITE). pack().utils. canvas3D = new MyCanvas3D(config).numFrames = arg. View view = new View().*. } public static void main(String[] args) { Arguments arg = new Arguments(args). public Main(String s) { super(s).

} } MyCanvas3D.0. view. 1.setPhysicalEnvironment(physicalEnvironment). bg. light.j3d.0). view. 1000)).4f.stopBehaviorScheduler(). view.0f.0f).0f). view.5).setEnable(true).PhysicalBody physicalBody = new PhysicalBody(). 0.addBranchGraph(viewBranch).setColor(new Color3f(1. light.compile().0. import com.addChild(light).85. We also do the scene // graph building. fog. // // The canvas on which the drawing occurs.java ////////////////////////////////////////////////////////////////////////////// // Virtual World (Java3D version) // by Jacob Marner.*.0). // ////////////////////////////////////////////////////////////////////////////// import java. light. view. frustumfar).0f. view. 0. 1. light.0. bg. alight. 0.setInfluencingBounds( new BoundingSphere(new Point3d(0. // Activate scene graph . // Disable all behaviors for speed. 1000)). 295 . 0.0. 0. 1. new Color3f(0. -1. PhysicalEnvironment physicalEnvironment = new PhysicalEnvironment().SCALE_EXPLICIT).0).0f. Feb 2002.compile(). // Add vague global ambient light (this is enabled // by default in OpenGL versions) AmbientLight alight = new AmbientLight(true.addBranchGraph(bg). locale. // Add fog BranchGroup bg = new BranchGroup(). This makes the // application somewhat faster and is only possible // because we do not use behaviors.setFieldOfView(Math. 0. 1000)).0. // Set viewing parameters to ensure correct view fustrum // I have avoided compatibility mode double frustumfar = 40. // Add global directional light DirectionalLight light = new DirectionalLight().0f. 0. locale. view.VIRTUAL_EYE).0f. bg.0f.*. LinearFog fog = new LinearFog(new Color3f(0.0. 0.utils.making it live locale. bg.sun.4f.setInfluencingBounds(new BoundingSphere(new Point3d(0.4f)).setPhysicalBody(physicalBody). 0.setBackClipDistance(frustumfar).awt.addBranchGraph(scene).PI / 2.0.setBackClipPolicy(View.VIRTUAL_EYE). viewBranch.setInfluencingBounds( new BoundingSphere(new Point3d(0.0f)).setFrontClipPolicy(View.addChild(fog).setScreenScalePolicy(View.setFrontClipDistance(0.addChild(alight).0).setDirection(0. We here // perform the drawing and timing. 0. view.universe. frustumfar * 0. view.

java.nextFloat() * interval. } private void startTimer() { starttime = new Date().j3d.exit(0). frame++. } private void stopTimer() { Date endTime = new Date(). public public public static final final final final static int WORLD_MAX_X static int WORLD_MAX_Y static int WORLD_MAX_Z int MAX_BOXES_PER_LEAF = = = = 100. if (frame == 0) startTimer(). System. javax.preRender(). // Generate a random number between 0 and maxValue. 296 . javax.postRender().image.j3d.sun. Date starttime. float timeelapsed = (endTime. public class MyCanvas3D extends Canvas3D { int frame = 0.utils. 100.geometry.*.*.*.out. if (frame >= numFrames) { stopTimer(). static int numBoxes.starttime.").*.util. } public void preRender() { display().import import import import import import com. 100. return minValue + offset. System. 10. java. static int numFrames.*.0f.sun. float maxValue) { float interval = maxValue .utils. } } Random rand.println("Elapsed time: " + timeelapsed + " sec. float offset = rand.media.minValue.j3d.").io.out. MyCanvas3D(GraphicsConfiguration graphicsConfiguration) { super(graphicsConfiguration). com.vecmath.println( "Average frame rate: " + ((float) numFrames) / timeelapsed + " fps. } public void postRender() { super.getTime()) / 1000.getTime() . super. } static public Texture2D texture. System.*. private float getRandomFloat(float minValue.

i < numBoxes. 297 .0f. imageWidth.scale = getRandomFloat(0. imageHeight).0f.0f). // adjust height as necessary image = loader.0f. boxData.1f. boxData[i]. boxData[i].public BranchGroup createSceneGraph() { // Generate numBoxes box data put them in array.rotation.position. boxData[i]. Generate each number // randomly. this).getHeight(). // compute this level if (imageWidth > 1) imageWidth /= 2. } // Load texture String filename = "glow. // Generate MipMaps int imageLevel = 0. for (i = 0.x = getRandomFloat(0.x = getRandomFloat(0.getWidth(). } int imageWidth = image. image). try { image = loader. WORLD_MAX_X). texture.getTime()).z = getRandomFloat(0. 360.BASE_LEVEL_LINEAR).0f.0f. } // Trilinear filtering texture. WORLD_MAX_Y). boxData[i]. root. TextureLoader.rotation. int i.getScaledImage(imageWidth.jpg".5f).position. WORLD_MAX_Z).0f). 360.0f). boxData[i]. rand = new Random(new Date(). pos(3). ImageComponent2D image = null.MULTI_LEVEL_LINEAR). populateScene(root. BoxData[] boxData = new BoxData[numBoxes].setMagFilter(Texture. Texture. ++i) { boxData[i] = new BoxData(). 1. 360.getImage().y = getRandomFloat(0. 0.setImage(imageLevel. // adjust width as necessary if (imageHeight > 1) imageHeight /= 2. // Create scene BranchGroup root = new BranchGroup().MULTI_LEVEL_MIPMAP. while (imageWidth > 1 || imageHeight > 1) { // loop until size: 1x1 imageLevel++. texture.0f. TextureLoader loader = new TextureLoader(filename.rotation. texture.setImage(imageLevel.GENERATE_MIPMAP.compile(). int imageHeight = image.exit(0).RGB. } catch (NullPointerException e) { System.setMinFilter(Texture. For each one // store scale(1). rotation(3). numBoxes).y = getRandomFloat(0. texture = new Texture2D(Texture. boxData[i].position. boxData[i]. imageHeight).z = getRandomFloat(0. image).

x.pow(boxData[c]. 298 . 0.boundingBox[0]. double yspan = boundingBox[1].z = candidate. if (candidate < boundingBox[0]. // Find and set bounding box by iterating through boxes. candidate = boxData[c].position.y + boxMaxDistFromCenter.z + boxMaxDistFromCenter.boxMaxDistFromCenter.y+ bcenter.y*bcenter. // If n is more than max_boxes if (numindex > MAX_BOXES_PER_LEAF) { int splitn.y) boundingBox[0]. if (candidate < boundingBox[0].z = candidate. boundingBox[1])).setBounds(new BoundingSphere(bcenter.position. No! It makes it slower but only a little. 2)).0f * (float) Math.z) boundingBox[1].y . ++c) { float boxMaxDistFromCenter = (float) Math. 0).2)) ) for (int c = startindex.position. candidate = boxData[c].x = candidate. if (candidate > boundingBox[1].y = candidate.scale(0.y. } // Does using a bounding sphere instead of bounding box make // it faster.boundingBox[0]. bcenter.x) boundingBox[1]. float candidate. /* Point3d bcenter = new Point3d(boundingBox[1]). bcenter.x . int startindex.5f).y . candidate = boxData[c]. if (candidate > boundingBox[1]. candidate = boxData[c].x .position.sub(boundingBox[0]). if (numindex > 2) { double xspan = boundingBox[1].boxMaxDistFromCenter. c < startindex + numindex.z*bcenter. WORLD_MAX_Y. boundingBox[0] = new Point3d(WORLD_MAX_X. if (candidate > boundingBox[1].position.boxMaxDistFromCenter.z).y = candidate. int numindex) { // First min box then max box Point3d[] boundingBox = new Point3d[2]. */ bg.x + boxMaxDistFromCenter.return root.x) boundingBox[0].scale. candidate = boxData[c].z) boundingBox[0].x = candidate.position. boundingBox[1] = new Point3d(0.x*bcenter. WORLD_MAX_Z). BoxData boxData[]. candidate = boxData[c].sqrt(2. I tried.setBounds(new BoundingBox(boundingBox[0].x+bcenter. bg.radius)). } private void populateScene( BranchGroup bg.z . double radius = Math.sqrt(bcenter.y) boundingBox[1]. if (candidate < boundingBox[0]. // (Boxes is centered about position and has a distance // from center of max sqrt(2*pow(scale.

0f. ++i) { Transform3D translate = new Transform3D().splitn). i < startindex + numindex. startindex + numindex .rotY(boxData[i]. startindex.mul(rotY).1. 1). } else { // Find center along max axis double center = boundingBox[0]. populateScene(bgright. if (xspan >= yspan && xspan >= zspan) { // Find center along max axis double center = boundingBox[0].x + xspan / 2. populateScene(bgleft. splitn = reorder(boxData. 0). } if (splitn == startindex) splitn = startindex + 1.boundingBox[0]. Transform3D rotZ = new Transform3D(). transform.rotation. center.rotation. startindex. splitn = reorder(boxData. Transform3D scale = new Transform3D(). boxData. transform. startindex.y). Transform3D transform = new Transform3D(). // Reorder box data according to center on the max axis.addChild(bgright).double zspan = boundingBox[1].z. rotX. center. bg. // Reorder box data according to center on the max axis. splitn = reorder(boxData. numindex.y + yspan / 2. center.z + zspan / 2.x).z .0f. } } } else { splitn = startindex + 1. BranchGroup bgright = new BranchGroup(). transform. 299 .set(boxData[i]. } else { if (yspan >= zspan) { // Find center along max axis double center = boundingBox[0].startindex).addChild(bgleft). boxData. startindex. splitn.0f. 2). numindex. } else { for (int i = startindex. if (splitn == startindex + numindex) splitn = startindex + numindex . Transform3D rotY = new Transform3D().position). splitn .rotX(boxData[i]. numindex. bg. Transform3D rotX = new Transform3D().mul(rotX). // Create nodes for each child BranchGroup bgleft = new BranchGroup(). rotY. // Reorder box data according to center on the max axis. translate.mul(translate).

1. if (numindex < 1) return 0. } } // Reorder boxData array according to value at given offset. while (j >= p && vector3fIndex(boxData[j].z).addChild(new VisualBox(boxData[i])).addChild(tg).scale). } else { while (j < r && vector3fIndex(boxData[j]. TransformGroup tg = new TransformGroup().y.position. double splitvalue. int numindex.assign(boxData[i]).position. transform. 2 gives z. 1 gives y.x. int n) { switch (n) { case 0 : return v. offset) > splitvalue) j--. offset) < splitvalue) 300 . tg.mul(rotZ). int offset) { BoxData temp = new BoxData().mul(scale).setScale(boxData[i]. int p = startindex. transform. if (i < j) { temp. case 2 : return v. while (i <= r && vector3fIndex(boxData[i]. static int reorder( BoxData boxData[].rotZ. bg. // Add some geometry under the transform group //tg. boxData[j]. int startindex.assign(boxData[j]). } } } static float vector3fIndex(Vector3f v.rotZ(boxData[i]. scale.4)). j--.rotation. case 1 : return v.setTransform(transform).assign(temp).1.z.addChild(new ColorCube(0.position. while (true) { i++. default : throw new ArrayIndexOutOfBoundsException(). boxData[i]. tg. int i = p . int j = r + 1. int r = startindex + numindex . offset) < splitvalue) i++. If offset is 0 then // x is used.

// We need to invert the matrix.setNormal(index. 301 . float c1.j++.java ////////////////////////////////////////////////////////////////////////////// // Virtual World (Java3D version) // by Jacob Marner. public class VisualBox extends Shape3D { private Geometry geometry. cameraPos. cameraDirection). float angle = (float) frame / 600. (WORLD_MAX_Z / 2. float t1. int index.vecmath. WORLD_MAX_Y / 2.set( (WORLD_MAX_X / 2.new Point3f(c1.setTextureCoordinate(0. float n1.0. } private void drawVertex(QuadArray g. float c3. if (j <= p) j = p. return j.lookAt(cameraPos.n2. Point3d cameraDirection = new Point3d().n3)). float n3) { g.sin(newAngle))). float n2.invert(). appearance = createAppearance(). g.c3)). float c2.t2)). 0). } } VisualBox. cameraDirection. // See the Java3D 1.viewTransformGroup.index.0f + radius * Math.0f. // ////////////////////////////////////////////////////////////////////////////// import javax. Transform3D v = new Transform3D(). Feb 2002. endPoint. v.setTransform(v).j3d. static private Appearance appearance = null.0f. Point3d endPoint = new Point3d().0f + radius * Math. float radius = 20. Main. 1. private void display() { // Move camera in circle around center of world. float newAngle = angle + 3. new Vector3f(n1. public VisualBox(BoxData b) { geometry = createGeometry().add(cameraPos.cos(angle)). upVector).set((Math. 0. 294-295.new TexCoord2f(t1. import javax. (Math. v.14f / 2.*. // // The generation of a single box in the scene graph. g.cos(newAngle)).0f.*.c2. Vector3d upVector = new Vector3d(0.sin(angle))). setAppearance(appearance). setGeometry(geometry).setCoordinate(index. endPoint.media. float t2.3 spec beta 1 p. } } } Point3d cameraPos = new Point3d().

i++.1.i++.1).0.1.3f.1.0.-1.-1).0.1. GeometryArray.1). drawVertex(g. drawVertex(g.i++.1.1.1.-1.-1. // new Color3f(0.-1.0.0f). return g.1.0.0).0.} private Geometry createGeometry() { // We do not refer geometry by reference since // that would prevent the elimination of // the transform node above the shape3d during // compilation of the scene graph (this optimization // is only applied in Java3D 1.1.0).-1.3f). // Ambient Emissive Diffuse Specular 302 .0.0. // Front side drawVertex(g. drawVertex(g.i++.TEXTURE_COORDINATE_2).1.0.1.0.0.1.1.1. if (appearance==null) { appearance = new Appearance().0).1.1.0. } private Appearance createAppearance() { // We refer Appearance by reference since is // required in order for the scene graph // compiler to merge Shape3D objects.0.1.0).0.-1.1.i++.1.NORMALS | GeometryArray.0).0f.0).-1.0f).-1.1.0.0).1.-1.i++.i++.-1.i++.1. // left side drawVertex(g.1.1.1. // Under side drawVertex(g.1.0.3 and later) QuadArray g = new QuadArray(4 * 6.COORDINATES | GeometryArray.1. // Back side drawVertex(g.0f.1.1.i++.1.0). drawVertex(g.1.i++.i++. // new Color3f(0.i++.1.0.1.1.0.1.0.0.-1.0f.-1.1. // Right side drawVertex(g. drawVertex(g.-1.0.0).1.0.1.-1.0.-1.0). drawVertex(g.0.0).1. drawVertex(g.-1.1.0.0.-1.0.0f).1.-1.-1. drawVertex(g.0.-1.1. // new Color3f(1. drawVertex(g.0.1.0.0).1.0.i++.0.0.1.-1.-1.0.0.0).i++.0.i++. drawVertex(g.i++.-1.0.1.i++.3f.-1. drawVertex(g.0.-1.1.1.1.-1.0f.0.0.1.0.1.-1.-1. // Top side drawVertex(g.i++.-1.0.i++.-1.0.-1).1.i++.-1).0f. int i=0.1.1.0.-1.0. drawVertex(g.0.1.i++.i++.0).0. drawVertex(g.0.-1.0). drawVertex(g.0.i++.-1.1).1.0.-1).0.1. drawVertex(g.-1. drawVertex(g.0.1.0.1.1.0f.-1.-1.1.-1.-1.-1.1.0.0).0.0.1.1.0.0.-1.1.0.-1.1. drawVertex(g.1).-1.1.1.1. drawVertex(g. // Set material properties Material material = new Material( new Color3f(0.i++.-1.0.0.1.-1.0.

0f. // Add texture appearance. int u) { lb = l. a[j] = aj1. if (! cmp. Object aj = a[j]. Object o2) { return (((Range)o1). } } // compare lower bound static final class LBComparator implements Comparator { public boolean compare(Object o1. } public String toString() { return "[" + lb + ". Comparator cmp) { int al = a. } return appearance.lb < ((Range)o2). for (int i=0. i<al. i++) for (int j=al. j--) { Object aj1 = a[j-1]. 1. } } } } final public class sort { static final class Range { int lb. aj)) { a[j-1] = aj.0f). appearance. appearance. interface Comparator { boolean compare(Object o1.SHADE_FLAT)).MODULATE).compare(aj1. ub = u.Date. TextureAttributes texattrib = new TextureAttributes()." + ub + "]". } final class Lib { static void sort(Object[] a. 1. // Turn on flat shading appearance.setColoringAttributes( new ColoringAttributes(1.setTextureAttributes(texattrib). int ub.setTextureMode(TextureAttributes. } } Edited “Java array” without specialization // generic bubble sort in Java // array version written Dec 99 by Ulrich Stern // Edited Feb 2002 by Jacob Marner import java.lb).0f.setTexture(MyCanvas3D.0f. 303 .texture). Range(int l.length-1. texattrib. Object o2).50.util.setPerspectiveCorrectionMode(TextureAttributes. texattrib. j>i.NICEST).setMaterial(material). ColoringAttributes.

. final LBComparator lb = new LBComparator().0f. Range[] a = new Range[N]. ub).starttime.out. i*13%1000). i++) a[i] = new Range(i*7%1000. Object o2) { return (((Range)o1). for (int i=0. Date starttime = new Date().} } // compare upper bound static final class UBComparator implements Comparator { public boolean compare(Object o1. Lib. Lib.println("sorting.println("Elapsed time: " + timeelapsed + " sec.length. float timeelapsed = ((float)(new Date()..ub < ((Range)o2). lb). final UBComparator ub = new UBComparator().getTime())) / 1000.").sort(a. System.getTime() .ub). } } public static void main(String[] args) { System."). final int N = 30000. i<a.sort(a. } } 304 .out.

Range() {} Range(int l. const Range* r) { return out << '[' << r->lb << '. a[j-1] = a[j]. N. const Range* b) const { return a->ub < b->ub. i<N. Comparator cmp) { for(int i=0. j--) if(!cmp(a[j-1]. } struct LBComparator { bool operator() (const Range* a. Edited “C++ pointer” // generic bubble sort in C++ // pointer version written Jan 00 by Ulrich Stern // Edited Feb 02 by Jacob Marner #include <iostream. i<n-1. ostream& operator << (ostream& out. i*13%1000)." << endl.h> template<class Object. for(int i=0. const Range* b) const { return a->lb < b->lb. int u): lb(l). mysort(a.2 we changed some benchmarks by Ulrich Stern. i++) for(int j=n-1.' << r->ub << ']'. cout << "Elapsed time: " << timeelapsed << " sec.. float timeelapsed = ((float)(clock() . j>i. Range* a[N]. LBComparator()). UBComparator()).11. } }.Appendix H: Edited third party benchmark In section 7." << endl.starttime)) / CLOCKS_PER_SEC.. clock_t starttime = clock(). class Comparator> void mysort(Object* a[]. struct UBComparator { bool operator() (const Range* a. cout << "sorting. N. a[j])) { Object* tmp = a[j-1].h> #include <time. int main() { const int N = 30000. 305 . mysort(a. ub(u) {} }. i++) a[i] = new Range(i*7%1000. } }. The modified benchmarks can be found here. } } struct Range { int lb. a[j] = tmp. ub. int n.

for (i=0.length-1.lb < o2. Range(int l. j>i. i++) inner(). int u) { lb = l. } Edited “Java array” with specialization // generic bubble sort in Java // array version written Dec 99 by Ulrich Stern // Edited Feb 2002 by Jacob Marner import java.compare(aj1. return 0.lb. static void inner() { for (int j=al. Range o2) { return o1." + ub + "]".a = a.cmp = cmp. static Comparator cmp. j--) { Range aj1 = a[j-1]. a[j] = aj1. Lib. } 306 . } final class Lib { static Range[] a. ub = u. al = a.for(int j=0. i<al. if (! cmp. Range o2). Comparator cmp) { Lib. interface Comparator { boolean compare(Range o1. } } final public class sort { // compare lower bound static final class LBComparator implements Comparator { public boolean compare(Range o1. } public String toString() { return "[" + lb + ". j++) delete a[j]. } } final class Range { int lb. aj)) { a[j-1] = aj.Date. int ub. } } } static void sort(Range[] a. Range aj = a[j]. static int al. static int i. j<N.util.

println("Elapsed time: " + timeelapsed + " sec. } } 307 .getTime())) / 1000.. Lib. final int N = 30000..out.ub < o2. final UBComparator ub = new UBComparator(). i*13%1000). Lib. final LBComparator lb = new LBComparator(). for (int i=0.} // compare upper bound static final class UBComparator implements Comparator { public boolean compare(Range o1.println("sorting. } } public static void main(String[] args) { System.starttime.sort(a.length. System.")."). Range[] a = new Range[N].sort(a. Date starttime = new Date(). lb).0f. i<a.getTime() . float timeelapsed = ((float)(new Date(). Range o2) { return o1. ub). i++) a[i] = new Range(i*7%1000.out.ub.

Appendix I: Benchmark results This section contains the results of the benchmarks in tabular form. Untweaked Particles Warm-up: 300 Test is done on Java 1.8 16 24 30 39 45 0 9 15 23 31 42 46 0 8.8 16 22 33 37 45 0 8.2 7 8 7 7 1 2 3 4 5 Particles Steps 700 53 53 50 53 52 53 8 800 60 61 58 61 60 60 7 900 67 68 67 68 61 67 7 1000 74 77 74 72 74 74 7 1100 82 80 81 84 80 81 7 1200 91 88 91 89 90 90 9 1300 93 102 98 98 97 98 8 1400 102 98 104 100 103 102 4 1500 111 117 107 109 106 109 7 1600 119 117 116 118 120 118 9 1700 118 123 128 125 126 125 7 1800 136 135 13 135 135 135 10 1900 141 142 141 141 137 141 6 2000 147 143 150 144 149 147 6 308 .8 16 23 31 38 45 First order derivation (x100) 8. The original Excel spreadsheet can be downloaded at the home page of this report.7 16 23 37 38 45 0 8. This data was used to generate the graphs used in chapter 7.4 serverjava -server 0 100 200 300 400 500 600 0 8.9 17 23 31 37 46 Median 0 8.8 7.

17 1100 103 105 103 103 105 103 0.31 0.6 6.12 800 57 56 57 57 57 57 0.14 600 56 57 58 58 57 57 0.1 13 21 32 48 65 86 113 5.85 3.6 5.6 6.23 0.3 14 21 6.6 8 11 14 18 5 0.2 12 21 33 48 66 86 111 0.024 600 9.3 3.1 3.07 600 28 29 29 29 29 29 0.4 2.1 0.04 900 21 21 21 21 22 21 0.03 0.3 0.3 1.05 1100 32 32 32 32 32 32 0.7 8.6 6.07 1400 52 52 53 52 52 52 0.13 900 70 71 71 70 71 71 0.039 0.07 1500 60 60 60 60 63 60 0.7 1.6 2.013 java 300 7.7 7 First order derivation (x100) 7.4 9.17 1000 90 87 89 85 88 88 0.021 0.18 0.6 6.8 8.06 Num Particles 1 2 3 4 5 Median First order derivation 200 1.06 1400 44 45 44 50 45 45 0.6 9.039 400 4.23 Num Particles 1 2 3 4 5 Median First order derivation Jet Num Particles 1 2 3 4 5 Median First order derivation 100 1.6 0.26 0.2 0.8 8.3 1 2 3 4 5 Particles Steps 400 27 28 27 28 27 27 6 java -server 500 33 33 34 33 33 33 6 600 40 41 40 40 39 40 7 700 48 47 46 46 46 46 6 800 52 52 54 52 54 52 6 900 58 59 54 58 59 58 6 1000 60 65 66 65 65 65 7 1100 70 72 71 71 73 71 6 1200 78 79 72 80 78 78 7 1300 84 85 86 86 86 86 8 1400 93 92 93 92 93 93 7 1500 90 99 99 97 91 97 4 1600 104 103 104 108 110 104 7 1700 111 112 111 110 111 111 7 1800 118 116 118 117 118 118 7 1900 123 125 123 124 124 124 6 2000 128 131 132 131 130 131 7 309 .26 0.3 100 1.93 2.Results: Run using 100 time steps of h=0.26 0.1 2.26 0.2 14 21 0 7.051 java 500 20 20 20 20 20 20 0.068 0.2 0.89 2.-checknull.1 6.4 2.89 2.3 1.22 0.1 3.3 1.4 2.3 3.04 Intel C++ 100 0.17 1100 105 106 106 106 106 106 0.6 5.3 1.04 1100 28 28 27 6 27 27 0.0234 JDK 1.1 6.1 1.4 1.4 0.6 5.23 0.9 8.6 9.2 12 21 33 48 66 86 112 5.8 4.1 4.079 400 25 25 25 25 26 25 0.09 700 41 40 40 41 41 41 0.036 800 17 17 17 17 17 17 0. Results are given in seconds with 2 significant digits to the nearest number of seconds.0026 1000 22 22 23 22 23 22 0.9 2.2 11 14 18 2 0.001.1 4.4 0.16 1000 90 90 91 90 90 90 0.11 500 40 39 39 39 39 39 0.044 300 14 14 14 14 14 14 0.1 1.6 5.3 1.22 800 105 104 104 104 104 104 0.1 1 1.017 200 6 6.018 500 6.04 1000 27 24 26 26 26 26 0.9 8.15 0.1 6.3 15 20 0 7.1 1.2 7.2 0.1 3.9 0.86 3.3 0.0084 300 2.059 java -server 400 15 14 14 14 14 14 0.25 900 134 135 135 133 134 134 0.25 1000 135 135 138 137 135 135 0.012 0.86 3.4 4.2 4.06 1200 38 38 38 38 38 38 0.4 9.1 11 14 18 3 0.-inline+ -genstackalloc+ 200 300 400 500 600 700 800 900 5.2 3.5 2.09 0.24 Tweaked Particles Warm-up: 400 Test is done on Java 1.3 6.9 2.06 1300 45 45 44 45 45 45 0.6 8 11 14 18 4 0.12 0.16 1200 129 129 129 126 129 129 0.18 700 78 79 79 79 78 79 0.-checktype.028 700 13 13 13 13 13 13 0.2 Num Particles 1 2 3 4 5 Median First order derivation 300 8.1 7.2 0.14 900 73 73 73 73 71 73 0.3 0.26 0.2 4.15 1200 121 121 123 123 123 123 0.2 12 21 32 47 66 86 111 5.4 0.1 12 21 33 48 64 88 109 5.9 8.05 1300 39 38 38 39 38 38 0.7 5.1 7.86 3.4 2.4 client 100 200 0.4 14 20 0 7.7 1.2 11 14 19 0.1 7.6 8.025 0.6 6.22 0.8 1.1 0.3 14 21 0 7.2 0.85 3.02 0.1 0.2 4.7 1.029 0.023 IBM 400 13 13 13 12 13 13 0.2 7.22 0.1 11 14 18 Median First order derivation 0.6 8.09 700 43 43 44 42 44 43 0.1 4.0022 0.4 9. MS C++ Num Particles 100 200 300 400 500 600 700 800 900 1 0.3 14 21 Median 0 7.4 server 100 200 2 4 2.07 1500 52 51 51 51 51 51 0.2 4.08 Num Particles 1 2 3 4 5 Median First order derivation JDK 1.6 5.6 2.86 3.4 server 200 300 0 100 0 7.0086 0.013 jc =all -checkindex.2 0.0068 0.7 8.05 1200 32 33 32 32 32 32 0.08 600 31 31 31 31 31 31 0.9 2 3.-multithread.015 0.3 12 21 33 47 66 87 111 5.7 1.22 0.12 800 55 53 54 55 54 54 0.045 500 22 22 22 22 22 22 0.7 0.

27 0.11 jc =all -checkindex.4 0.3 6.4 0.5 1.0079 300 2.2 0.0059 0.25 1 0.-checktype.17 600 10 10 10 10 10 10 0.4 0.3 7 7 7 0.05 1000 26 29 27 28 26 27 0.03 700 14 14 14 14 14 14 0.4 4.26 0.99 0.9 7 9.2 8.0041 Jet Num Particles 1 2 3 4 5 Median First order derivation 100 0.06 1700 64 57 64 57 58 58 0.06 1100 34 34 34 34 34 34 0.0025 0.4 0.6 12 16 19 23 Median 0.24 0.017 java -server 400 500 7.04 700 21 21 21 21 21 21 0.9 7 9.1 2.4 0.9 9.5 11 7.04 1400 50 49 50 50 50 50 0.06 1200 44 43 44 44 44 44 0.05 1300 33 36 35 25 19 33 0.9 7 9.78 1.5 2.07 1000 42 43 42 42 42 42 0.4 1.09 1200 62 62 61 61 62 62 0.014 1200 28 28 28 28 28 28 0.41 1.037 java 400 4.4 server 100 200 1.025 0.8 9.11 1400 86 85 85 85 84 85 0.8 10 13 18 22 29 1.7 13 18 23 28 1 2.03 600 12 12 12 12 12 12 0.12 1300 58 59 58 60 58 58 0.1 9.99 0.2 9 9.4 6.78 1.6 2.1 9.3 11 7.05 0.08 1500 59 58 59 58 57 58 0.12 1500 98 102 102 101 102 102 0.013 0.27 0.7 1.09 1600 74 74 74 74 74 74 0.26 1 0.1 2.-inline+ -genstackalloc+ 600 700 800 900 1000 200 300 400 500 1.08 1300 52 52 53 52 52 52 0.06 1100 40 40 40 40 40 40 0.8 3.011 0.1 1.022 500 8.016 IBM Num Particles 1 2 3 4 5 Median First order derivation 100 0.4 4.024 0.3 6.4 4.4 0.3 4.19 0.19 0.06 900 34 34 34 34 34 34 0.023 600 15 15 15 16 15 15 0.06 1000 33 33 33 33 33 33 0.4 1.06 1400 67 68 67 67 68 67 0.9 7.05 1500 45 44 50 44 47 45 0.3 5.4 0.4 1.06 800 27 27 27 27 27 27 0.09 1700 83 83 84 83 83 83 0.08 1600 67 25 65 66 66 66 0.5 4.7 12 16 19 23 5 0.0027 200 1.0083 0.-checknull.1 5.19 0.7 3.1 4.7 4.2 0.3 2.1 4.0102 0.6 74 3 0 35 60 71 4 0 32 54.3 4.07 1200 49 53 50 52 55 52 0.8 9.1 9.2 0.3 0.001.7 0.27 0.2 6.26 0.2 1.99 0.004 0.7 1 2.05 1200 41 41 41 41 41 41 0.2 1.08 1500 66 64 65 65 65 65 0.026 0.3 6.07 Num Particles 1 2 3 4 5 Median First order derivation 300 2.2 1.08 1200 40 38 36 36 40 38 0.3 2.0131 400 4 4 4 4.39 1.4 4.04 Intel C++ 100 200 0.77 1.2 0.41 0.1 0.7 1.021 500 7.2 11 7.4 0.7 9.09 1600 80 80 80 83 80 80 0.4 server 0 500 1000 1500 1 0 33 55 68 2 0 34 54.013 0.1 7 7.018 0.4 4.2 0.2 0.07 1400 56 56 57 56 56 56 0.2 1.4 4.08 1700 74 74 74 75 64 74 0.09 1500 70 70 70 69 70 70 0.07 1300 48 48 49 48 48 48 0.2 6.3 8.-multithread.2 8.1 4 4 0.4 1.09 1500 78 78 79 78 79 78 0.79 1.08 1100 52 51 53 51 51 51 0. Results are given in seconds with 2 significant digits to the nearest number of seconds.2 5.2 2.29 0.45 0.4 4.8 4.05 900 27 27 27 27 26 27 0.8 3.6 13 18 26 32 4.7 4.5 2.6 13 18 23 28 1.2 5.6 2.05 0.0074 JDK 1.05 1400 38 38 39 38 38 38 0.9 6.5 12 16 19 2 3 0.2 5.1 2.4 2.3 11 7.8 13 18 26 30 1 2.04 0.1 70 5 0 35 57 74 Median 0 34 55 71 First order derivation (x500) 34 21 16 World size Steps 2000 86 88 84 84 86 86 15 -server -Xmx200MB 2500 3000 99 114 103 103 101 113 106 111 102 121 102 113 16 11 3500 137 112 134 124 115 124 11 4000 137 153 144 134 137 137 13 4500 151 166 165 173 162 165 28 5000 188 157 174 176 178 176 11 310 .08 1400 61 61 60 61 61 61 0.7 0.7 9.5 12 16 19 24 3.1 4.6 2.99 0.09 Untweaked Life Warm-up: 200 Test is done on Java 1.6 0.1 9.01 JDK 1.25 0. MS C++ Num Particles 100 200 300 400 500 600 700 800 900 1000 1100 1 0.25 0.03 1300 43 43 42 42 42 42 0.4 client 100 200 0.3 6.1 2.2 6.038 700 16 16 16 16 16 16 0.8 9.4 6.7 3 4.019 0.1 2.1 8.3 11 7.021 0.1 2.2 6.06 1000 30 30 30 30 30 30 0.06 1100 36 36 36 36 36 36 0.022 600 9 9.8 7.3 11 0.3 6.6 1.19 0.3 4.4 0.3 2.029 800 16 16 17 16 17 16 0.04 800 19 18 18 18 18 18 0.4 4.77 1.04 900 21 21 21 21 21 21 0.2 8.08 Num Particles 1 2 3 4 5 Median First order derivation 300 3 3 3 3 3 3 0.6 2.41 0.Results: Run using 100 time steps of h=0.017 java 500 6.1 1700 90 91 91 95 90 91 0.029 0.11 Num Particles 1 2 3 4 5 Median First order derivation 300 4.7 13 18 23 29 1.6 12 17 20 23 4 0.11 1300 73 73 73 73 73 73 0.7 4.26 0.79 1.03 0.8 First order derivation 0.033 0.04 800 21 21 21 21 21 21 0.07 1600 51 51 50 51 51 51 0.016 400 5.06 1100 34 36 35 34 35 35 0.4 0.0019 0.8 3 4.6 12 16 19 24 2 0.1 2.3 2.029 700 12 12 12 12 12 12 0.029 0.41 0.04 900 24 24 24 24 24 24 0.

5 9.17 900 98 99 97 97 100 98 0.06 0.2 1.24 World size 1 2 3 4 5 Median First order derivation 300 6.2 1500 165 165 165 165 165 165 0.49 0.27 0.3 2.4 8 14 24 46 57 86 124 154 204 3.11 0.58 0.3 0.03 1200 28 28 28 28 28 28 0.3 0.53 2.22 World size 1 2 3 4 5 Median First order derivation Jet World size 1 2 3 4 5 Median First order derivation 100 0.4 6.19 0.09 1200 88 90 98 87 88 88 0.3 0.12 1000 71 71 71 71 71 71 0.86 0.7 3.06 1400 34 34 34 36 34 34 0.-multithread.7 7.3 2.7 7.5 Tweaked Life Warm-up: World size Steps 300 Test is done on Java 1.04 1300 30 30 30 30 30 30 0.3 4.4 server 100 200 0.0255 0.59 2.7 7.2 6.6 7.2 1.6 3.7 2.7 0.03 1000 17 17 17 17 17 17 0.4 5.13 0.04 1500 46 46 46 45 46 46 0.3 6.033 900 15 15 15 15 15 15 0.11 900 58 58 58 59 58 58 0.52 2.59 0.2 4.037 java -Xmx200MB 400 500 12 18 12 18 12 18 12 18 12 18 12 18 0.52 2.4 8 14 24 43 59 85 124 157 202 86 124 149 202 3.1 0.035 600 24 23 21 23 21 23 0.3 2.6 0.84 0.4 6.06 0.Results: World size 1 2 3 4 5 Median First order derivation 100 0.16 1200 106 106 106 106 107 106 0.7 9.0208 JDK 1.7 0.4 1.12 0.16 0.4 8 14 24 43 60 86 125 150 204 3.2 6.4 0.2 1.03 1000 20 20 20 20 20 20 0.3 6.7 0.7 8.88 0.4 1.3 4.12 900 53 49 53 49 47 49 0.95 0.057 java -server -Xmx200MB 400 500 11 17 11 17 11 17 11 17 11 17 11 17 0.7 8.52 2.13 1100 87 87 87 87 87 87 0.1 0.6 2.046 0.02 700 8.4 9.06 800 42 41 38 41 41 41 0.03 1200 24 24 24 24 25 24 0.6 0.6 0.4 8 14 24 43 59 0.2 1.61 2.0052 0.49 0.12 0.4 1.6 0.09 600 25 25 25 26 25 25 0.1 6.011 500 3.15 1400 141 141 141 141 141 141 0.0071 400 2.38 0.09 0.049 java -Xmx200MB 400 500 19 28 17 28 17 28 19 28 19 28 19 28 0.053 600 43 40 43 40 43 43 0.4 5.3 4.0085 jc =all -heaplimit=209715200 -checkindex.06 Worldsize 1 2 3 4 5 Median First order derivation JDK 1.08 1000 59 65 68 57 65 65 0.5 9.85 0.013 600 5.4 9.6 5.7 3.06 700 28 29 29 34 29 29 0.2 4.14 1300 98 116 103 101 102 102 0.08 World size 1 2 3 4 5 Median First order derivation 200 0.7 2.-inline+ -genstackalloc+ 200 300 400 500 600 700 800 900 1000 1100 3.0039 300 1.28 1200 180 183 190 186 188 186 0.93 0.2 0.97 0.6 0.-checknull.05 1100 23 23 23 23 23 23 0.92 0.2 1.023 900 13 13 13 13 13 13 0.6 0.4 6.1 0.7 2.4 client 100 200 0.49 0.16 1100 79 75 72 73 74 74 0.15 700 59 59 59 58 59 59 0.14 1400 118 137 119 118 116 118 0.2 4.6 6.06 0.25 1100 153 150 151 151 150 151 0.0059 0.51 2.3 6.095 0.5 5.2 5.83 0.3 0.13 0.2 4.016 600 6.59 2.3 0.0093 200 4.5 5.6 3.06 1400 39 38 39 38 38 38 0.93 0.6 0.52 2.13 0.5 0.3 6.04 1500 40 44 41 40 40 40 0.4 8 14 24 43 59 3.3 0.4 server 1000 1500 0 500 1 0 14 28 41 2 0 14 28 30 3 0 14 28 40 4 0 14 29 37 5 0 14 28 41 14 28 40 Median 0 14 14 12 First order derivation (x500) 2000 55 55 55 54 55 55 15 -server -Xmx200MB 2500 3000 71 82 67 81 70 84 51 84 69 83 69 83 14 14 3500 97 98 96 98 97 97 14 4000 110 111 109 110 108 110 13 4500 126 125 126 124 124 125 15 5000 138 99 140 138 139 138 13 311 .024 800 12 12 12 12 12 12 0.5 0.22 1000 123 124 123 124 121 123 0.07 700 35 35 35 35 35 35 0.7 2.7 8.3 4.16 1500 134 140 140 134 151 140 0.2 0.7 0.0327 300 9.49 0.7 0.35 100 0.53 2.0045 300 1.19 1300 121 121 121 120 122 121 0.04 1100 20 20 20 20 20 20 0.1 4.1 0.05 1300 34 34 34 34 34 34 0.023 800 10 10 10 10 10 10 0.9 9 8.4 1.6 3.0082 400 2.58 0.3 6.85 0.13 0.4 5.4 1.7 2.3 2.-checktype.5 8 14 24 43 61 86 124 154 204 85 124 154 207 3.4 0.58 0.16 800 76 76 76 76 76 76 0.0201 IBM 300 6.59 2.018 700 7.1 800 46 46 46 46 45 46 0.59 0.1 0.7 7.3 6.58 0.001 200 0.2 2.6 0.3 4.93 0.6 0.013 500 4.7 0.49 0.0013 Intel C++ 100 0.1 0.

0015 0.8 0.02 1600 19 18 18 19 19 19 0.1 0.-checknull.04 1500 23 28 27 27 27 27 0.5 0.3 2.9 7.9 Median 0 2.2 9.7 7.01 1200 8 8 8 27 8 8 0.3 5.3 2.13 0.4 2.006 900 7.44 0.8 4.03 1200 19 19 19 19 19 19 0.-checktype.0009 0.6 1 2.1 0.5 2.8 7.1 2.8 1 2 1.2 6.7 7.8 7.6 12 14 17 20 0.57 1.1 0.01 1000 6 6 6 5 6 6 0.9 10 13 First order derivation 0.8 7.2 6.15 0.9 4.5 1.022 0.13 0.14 0.34 0.Results: Run using 10 steps.6 9.04 1400 26 26 26 26 26 26 0.11 0.1 1 1.9 2.97 1.75 0.4 2.9 9.6 2.04 1300 28 28 28 28 28 28 0.2 java -server 2500 11 12 11 12 12 12 2.03 1100 20 20 20 20 20 20 0.-multithread.6 0.2 1 5.6 9.7 2.09 0.3 3.5 4 5.6 4.01 1400 10 11 11 11 11 11 0.08 0.011 0.02 1400 20 20 19 19 19 19 0.9 4.1 3.15 0.0061 java -Xmx200MB 400 500 1.05 1700 49 49 49 49 49 49 0.016 800 4.04 1900 22 28 29 28 28 28 0.04 1700 36 36 35 36 15 36 0.6 7.02 2000 22 22 22 26 21 22 0.03 World size 1 2 3 4 5 Median First order derivation 300 0.4 2.01 1100 7 7 8 7 7 7 0.02 600 2.15 0.1 0.1 1.2 5.4 server 100 200 0.06 1800 1900 2000 2100 World size 1 2 3 4 5 Median First order derivation 300 1.1 1 1.1 0.5 2.2 0.9 4.01 1300 9 9 9 9 9 9 0.6 7.03 1600 31 29 32 31 18 31 0.1 0.3 2.7 12 14 17 20 0.014 1100 11 11 11 11 7.2 6.006 0.9 0.01 1300 12 8.017 0.44 0.5 2.6 7.5 7.2 4.5 0.1 1 1.45 1 0.014 0.42 0.45 1 1.3 5.5 2.021 0.9 10 13 4 0.01 600 3.73 0.0035 0.03 Intel C++ 100 200 0.012 900 5 5 5 3 14 5 0.04 1900 36 35 19 36 36 36 0.4 client 100 200 0.6 12 14 17 20 0.007 800 11 4 4 3 4 4 0.6 2.2 6.03 1500 30 30 30 30 30 30 0.34 0.0056 400 1.66 0.4 server 0 500 1000 1500 0 2.04 Worldsize 1 2 3 4 5 Median First order derivation 300 1 1 1 1 0.9 3 4.9 0.1 3.2 0.9 2 1.6 1.05 jc =all -heaplimit=209715200 -checkindex.028 1000 13 13 13 13 13 13 0.8 2.-inline+ -genstackalloc+ 200 300 400 500 600 700 800 900 1000 1100 1200 1300 0.35 0.15 0.13 0.3 0 2.7 2.2 5.05 1500 38 38 38 38 38 38 0.8 2.45 1.7 4.5 4 5.021 0.63 0.2 8.9 3.026 800 5.1 3.0057 0.33 0.012 1200 10 10 10 10 10 10 0.0035 JDK 1.9 4.1 0.015 0.03 1800 32 32 22 26 32 32 0.03 1300 22 23 23 23 23 23 0.9 4.006 700 2.02 1400 14 14 13 14 14 14 0.09 0.1 3.34 0.9 10 13 Median 0.2 4.8 7.02 900 11 10 11 11 11 11 0.5 7.45 1 0.5 4.9 8.8 2.2 1.0027 0.04 1200 24 24 24 24 24 24 0.8 6.02 1000 8.32 0.73 0.8 1.9 9 8.02 1800 25 25 27 15 25 25 0.014 700 6.05 1800 40 32 40 39 25 39 0.7 1.2 8.34 0.04 1400 33 33 33 33 33 33 0.6 2.9 5.7 5.018 0.5 4.2 13 0.8 2.003 900 6.012 0.01 2100 24 23 24 24 24 24 0.9 10 13 3 0.56 0.3 4.8 0.1 3.7 2.5 0.8 2.1 0.33 0.8 0.5 1.9 0.2 0.18 1 1.2 0 2.0013 0.9 1.6 9.01 1600 11 14 14 14 14 14 0.3 1.04 1600 35 35 36 35 35 35 0.1 0.6 5.001 0.44 1.3 3.7 1.02 1300 11 15 15 15 11 15 0.7 2.005 700 4.6 3000 14 14 14 14 14 14 2 3500 16 16 16 16 16 16 2 4000 18 19 18 18 18 18 2 4500 20 21 21 21 20 21 3 5000 23 22 22 23 22 22 1 5500 25 24 24 24 24 24 2 6000 30 28 27 27 29 28 4 6500 29 29 31 32 31 31 3 7000 33 31 33 32 32 32 1 7500 34 33 33 33 34 33 1 8000 35 37 35 37 37 37 4 8500 38 37 37 37 40 37 0 9000 40 40 41 41 42 41 4 9500 42 42 41 42 44 42 1 10000 47 43 44 43 44 44 2 312 .57 1.5 4.1 0 2.001 200 0.73 0.9 4.89 1.0073 0.09 0.9 4.021 1200 13 13 10 13 6.1 1 1 1 0.1 0.83 1 0.57 1.72 0.017 800 8.5 4.0029 JDK 1.4 1 2 3 4 5 2000 9.016 1100 9 9 9 9 9 9 0.2 6.35 0.8 7.9 4.43 0.009 500 3.3 6.3 5.5 9.1 0.4 0.8 1.44 0.8 2.2 5.6 2.008 0.67 0.3 2.8 7.5 2.5 4 5.3 0.7 4.3 5.1 1.1 0.13 0.015 0.7 7.5 4 5.4 7.3 2.9 9 8.2 8.02 0.7 7.9 4.36 0.6 9.03 1400 23 10 24 24 24 24 0.63 0.02 1500 12 12 12 12 12 12 0.4 9.39 0.7 12 12 12 0.0044 0.9 0.3 2.7 7.35 0.05 1600 43 43 43 43 43 43 0.1 0.1 0.14 0.03 1900 21 21 21 13 21 21 0.44 0.35 0.01 1000 7.03 1600 25 26 25 15 26 25 0.05 1700 39 39 39 39 39 39 0.9 10 13 2 0. MS C++ World size 100 200 300 400 500 600 700 800 900 1 0.57 1.0025 300 0.13 0.34 0.8 4.2 6 6.02 1100 16 16 16 16 16 16 0.7 12 14 12 8.2 5.6 8.6 11 0.023 0.2 8.5 2.7 4.11 0.0019 IBM World size 1 2 3 4 5 Median First order derivation 100 0.02 1700 16 16 16 16 16 16 0.8 7.5 4 5.02 1500 16 16 17 16 16 16 0.005 0.0028 1000 16 16 16 16 16 16 0.4 0.8 7.5 4 5.3 0.6 9.9 2 1.3 3.7 4.2 2 1.4 7.5 4.2 0.005 java -Xmx200MB 400 500 1.03 1700 22 21 21 22 21 21 0.0039 java -server -Xmx200MB 400 500 600 0.1 0.79 0.9 4.008 700 4.3 1.13 0.59 0.9 4.3 4.03 0.57 1.2 5.2 0 2.02 1800 19 19 20 20 19 19 0.4 6.1 1.1 1.0055 0.5 6.1 1.04 2000 39 39 39 34 38 39 0.1 0.1 0.2 8.44 0.39 0.8 7.7 7.5 7.09 0.03 1700 28 28 28 28 19 28 0.001 Jet World size 1 2 3 4 5 Median First order derivation 100 0.8 7.2 7 2.9 2.5 1.03 2000 32 67 32 33 33 33 0.09 0.57 1.3 5.7 7.7 12 11 17 20 0.1 0.8 7.04 1500 16 22 23 18 22 22 0.8 6.6 9.1 2.012 600 4. Results are given in seconds with 2 significant digits to the nearest number of seconds.9 10 13 5 0.2 First order derivation (x500) 2.5 0.03 Untweaked 3D Demo Warm-up: Boxes Frames 1000 Test is done on Java 1.6 5 6.7 12 16 18 20 0.

01 512 19 19 19 19 19 19 0.02 1024 22 23 21 22 23 22 0.99 32768 173 174 175 172 175 174 1.2 16384 94 94 94 99 94 94 0.72 Tweaked 3D Demo Warm-up: Boxes Frames 1000 Test is done on Java 1.11 8192 59 60 58 57 59 59 0.01 512 19 19 19 19 20 19 0.04 2048 30 30 29 30 30 30 0.9 13 16 17 18 26 0 4.02 1024 24 23 24 24 23 24 0.69 GL4Java Num Boxes 1 2 3 4 5 Median First order derivation JDK 1.04 2048 30 29 30 29 30 30 0.74 Num Boxes 1 2 3 4 5 Median First order derivation 64 16 16 16 16 16 16 0 128 17 17 17 17 17 17 0.4 32768 170 171 171 173 169 171 0.01 512 19 19 19 19 20 19 0.03 2048 29 28 30 28 27 28 0.07 4096 39 37 39 40 39 39 0.19 1024 23 22 22 22 22 22 0.4 server 32 16 16 16 16 16 16 0.68 Num Boxes 1 2 3 4 5 Median First order derivation 64 16 16 16 16 16 16 0.22 2048 30 28 28 28 29 28 0.09 8192 61 58 58 59 60 59 0.16 128 16 16 16 17 17 16 0.01 128 16 16 16 16 16 16 0 256 17 18 17 17 17 17 0.06 4096 38 38 38 38 38 38 0.3 12 16 18 First order derivation (x100) 7.18 java -server 256 18 18 17 17 18 18 0.61 16384 100 98 99 99 100 99 0.82 512 19 19 19 20 19 19 0.39 8192 61 62 60 62 61 61 0.15 64 16 16 16 16 16 16 0.28 4096 39 40 39 38 39 39 0.4 serverjava -server 0 200 400 600 800 1000 1200 0 8.2 16384 99 99 99 99 98 99 0.3 4.16 64 16 16 16 16 16 16 0 128 17 17 17 17 17 17 0.01 java 64 16 16 16 16 16 16 0.01 512 20 20 20 20 20 20 0.3 12 16 12 18 23 0 7.06 4096 42 40 41 42 42 42 0.02 1024 22 22 22 22 22 22 0.23 16384 112 112 114 113 112 112 0.4 client 32 16 15 11 12 16 15 0.16 256 18 17 17 18 18 18 0.01 java 256 18 18 18 18 18 18 0.47 32768 193 194 194 195 194 194 0.06 4096 38 39 39 39 38 39 0.03 2048 27 28 28 27 28 28 0.2 16384 95 96 94 93 94 94 0.1 15 20 19 22 19 22 Median 0 7.7 4 2 1 3 1 2 3 4 5 1400 25 22 23 23 24 23 1 1600 24 28 25 25 26 25 2 1800 27 26 24 30 30 27 2 2000 31 31 31 32 30 31 4 2200 32 33 29 31 33 32 1 2400 32 31 33 35 34 33 1 2600 35 32 34 37 35 35 2 2800 37 35 36 36 35 36 1 3000 35 38 36 37 35 36 0 3200 38 37 37 40 36 37 1 3400 41 40 42 40 40 40 3 3600 39 42 40 41 40 40 0 3800 42 44 41 42 41 42 2 4000 44 42 46 44 42 44 2 313 .Results: Num Boxes 1 2 3 4 5 Median First order derivation MS C++ 32 14 14 16 16 15 15 0.12 8192 65 65 64 65 65 65 0.15 Intel C++ 32 15 16 16 15 15 15 0.1 8192 60 58 56 59 58 58 0.01 128 17 16 16 16 16 16 0 256 17 17 17 17 17 17 0.15 JDK 1.5 7.35 32768 162 159 162 164 160 162 0.01 1024 23 24 23 22 22 23 0.36 32768 163 163 162 163 165 163 0.3 13 19 18 19 21 0 7.7 12 18 18 20 20 0 4.16 IBM Num Boxes 1 2 3 4 5 Median First order derivation 32 16 16 16 16 16 16 0.

3 First order derivation 0.01 1024 19 20 20 20 19 20 0.1 2048 56 55 57 56 57 56 0.04 0.-checknull.3 8192 138 135 134 134 134 134 0.-inline+ -genstackalloc+ 64 128 256 512 1024 2048 4096 8192 16384 32768 I was unable to get Jet 2.1 to work with Java3D 1.01 256 16 16 16 16 16 16 0 java -server 256 16 16 16 16 16 16 0 java 64 16 15 16 16 15 16 0.24 Num Boxes 1 2 3 4 5 Median First order derivation 128 36 35 35 36 34 35 -0.83 512 17 17 17 17 17 17 0.01 0.18 4096 79 79 78 77 79 79 0.11 Num Boxes 1 2 3 4 5 Median First order derivation jc =all -checkindex.56 16384 165 273 266 272 282 272 1.-checktype.-multithread. frames of animation) Results are given in seconds with 2 significant digits to the nearest number of seconds.01 512 17 17 17 17 17 17 0.17 16384 88 88 87 87 87 87 0.04 4096 32 30 32 34 33 32 0.15 Jet Num Boxes 1 2 3 4 5 Median First order derivation #NUM! #NUM! 32 java 128 16 16 16 16 16 16 0.1 only supports Ja #NUM! #NUM! #NUM! #NUM! #NUM! #NUM! #NUM! #NUM! #NUM! #NUM! #NUM! #NUM! #NUM! #NUM! #NUM! #NUM! #NUM! #NUM! 314 .25 1024 32 31 32 31 31 31 0.4 client Java3D 32 64 19 19 19 19 19 19 19 19 19 19 19 19 0.03 java -server 256 39 36 37 39 39 39 0.38 32768 166 162 165 165 166 165 0. MS C++ Num Boxes 32 64 128 256 512 1024 2048 4096 8192 16384 1 15 15 16 16 17 19 24 32 48 77 2 20 23 32 46 74 15 15 15 16 17 3 15 15 15 16 17 20 22 32 46 75 4 15 15 15 16 17 19 24 32 49 79 5 19 23 33 47 78 15 15 15 16 17 Median 15 15 15 16 17 19 23 32 47 77 0 0 0.59 Num Boxes 1 2 3 4 5 Median First order derivation 64 15 15 15 15 15 15 0 128 15 15 15 15 12 15 0 256 9 15 16 16 16 16 0.62 GL4Java Num Boxes 1 2 3 4 5 Median First order derivation JDK 1.01 1024 19 20 20 19 20 20 0.15 Intel C++ 32 15 15 15 15 15 15 0.08 8192 46 47 46 47 45 46 0.35 8192 145 147 151 145 150 147 0.4 server Java3D 32 64 32 38 35 40 35 36 32 36 33 34 33 36 0.03 2048 24 22 30 30 25 25 0.03 2048 25 22 24 23 23 23 0.09 2048 69 72 70 69 69 69 0.03 IBM Java3D 32 23 24 29 23 23 23 0.23 Jet Num Boxes 1 2 3 4 5 Median First order derivation #NUM! #NUM! 32 128 19 20 19 20 20 20 0.02 2048 23 24 24 24 25 24 0.18 16384 95 93 93 94 93 93 0.18 4096 99 100 105 98 102 100 0.08 8192 47 48 49 54 50 49 0.09 0.16 16384 85 88 84 83 82 84 0.08 8192 50 50 50 51 49 50 0.01 512 29 30 27 28 28 28 0.15 0.-checktype.33 32768 142 141 141 143 140 141 0.14 16384 78 80 78 79 80 79 0.02 1024 20 19 19 20 20 20 0.31 8192 161 164 160 160 164 161 0.55 16384 260 257 260 258 257 258 1.43 32768 174 175 176 178 178 176 0.78 Num Boxes 1 2 3 4 5 Median First order derivation 128 16 16 16 18 16 16 0 512 18 18 17 21 21 18 0.01 512 42 41 42 42 44 42 0.03 2048 24 24 24 22 23 24 0.03 1024 52 51 51 50 50 51 0.61 16384 272 271 273 268 272 272 1.03 4096 32 32 33 32 30 32 0.-checknull. Jet 2.4 NIO library.19 0 JDK 1.02 0.4 client GL4Java 32 64 15 15 15 15 15 15 15 16 14 15 15 15 0.Results: Run using 5000 time steps (i.04 4096 33 32 32 37 33 33 0.04 java 64 21 21 24 24 24 24 0.-multithread.76 Num Boxes 1 2 3 4 5 Median First order derivation jc =all -checkindex.4 server GL4Java 32 64 15 16 16 16 15 16 16 16 14 16 15 16 0.e.18 4096 91 91 91 89 93 91 0.15 0 JDK 1.3 because uses the JDK 1.-inline+ -genstackalloc+ 64 128 256 512 1024 2048 4096 8192 16384 32768 I was unable to get Jet to work with gl4java for unknown reasons #NUM! #NUM! #NUM! #NUM! #NUM! #NUM! #NUM! #NUM! #NUM! #NUM! #NUM! #NUM! #NUM! #NUM! #NUM! #NUM! #NUM! #NUM! Java3D Num Boxes 1 2 3 4 5 Median First order derivation JDK 1.01 IBM GL4Java 32 15 15 15 15 15 15 0.33 0.02 1024 20 22 23 20 21 21 0.01 java -Xmx64MB 256 512 20 23 20 23 20 22 21 23 20 23 20 23 0 0.15 0.03 1024 39 39 38 38 38 38 0.01 0.15 32768 136 136 133 136 135 136 0.01 128 24 24 24 24 24 24 0 256 25 25 26 26 25 25 0.08 2048 51 49 49 47 48 49 0.01 128 16 15 16 16 16 16 0 256 17 16 16 16 16 16 0 512 18 17 18 17 18 18 0.09 8192 49 47 49 50 48 49 0.35 32768 160 163 158 157 164 160 0.04 4096 33 31 32 33 32 32 0.

Sign up to vote on this title
UsefulNot useful