Professional
Papervision3D Part I: Getting Started Chapter Chapter Chapter Chapter
1: 2: 3: 4:
Understanding Flash3D ..................................................................3 Getting Started With Papervision3D .............................................33 Rezzing Primitives........................................................................75 Adding Materials ...................................................................... 127
Part II: Working with Models and Data Chapter Chapter Chapter Chapter
5: 6: 7: 8:
Creating 3D Models .................................................................. 173 Working with Particle Systems .................................................. 221 Geocoding, XML, and Databases ............................................... 253 Gliding on AIR........................................................................... 285
Part III: Building Games and Websites Chapter Chapter Chapter Chapter
9: Incorporating 3D Physics .......................................................... 321 10: Building 3D Games for Wii....................................................... 365 11: Integrating the Flash Media Server.......................................... 417 12: Developing 3D Websites ......................................................... 459
Part IV: Extending PV3D and Beyond Chapter Chapter Chapter Chapter
13: 14: 15: 16:
Making 3D Movies .................................................................. 491 Taking Virtual Tours ................................................................ 521 Adding Services...................................................................... 553 Flash & Math: Exploring Flash 10 and Beyond ......................... 585
Appendix A: Pocket Reference ....................................................................623 Appendix B: Companion Website .................................................................693 Index .........................................................................................................697
Professional
Papervision3D Michael Lively
A John Wiley and Sons, Ltd., Publication
Professional Papervision3D This edition first published 2010 © 2010 John Wiley & Sons, Limited Registered office John Wiley & Sons Ltd, The Atrium, Southern Gate, Chichester, West Sussex, PO19 8SQ, United Kingdom For details of our global editorial offices, for customer services and for information about how to apply for permission to reuse the copyright material in this book please see our website at www.wiley.com. The right of the author to be identified as the author of this work has been asserted in accordance with the Copyright, Designs and Patents Act 1988. All rights reserved. No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form or by any means, electronic, mechanical, photocopying, recording or otherwise, except as permitted by the UK Copyright, Designs and Patents Act 1988, without the prior permission of the publisher. Wiley also publishes its books in a variety of electronic formats. Some content that appears in print may not be available in electronic books. Designations used by companies to distinguish their products are often claimed as trademarks. All brand names and product names used in this book are trade names, service marks, trademarks or registered trademarks of their respective owners. The publisher is not associated with any product or vendor mentioned in this book. This publication is designed to provide accurate and authoritative information in regard to the subject matter covered. It is sold on the understanding that the publisher is not engaged in rendering professional services. If professional advice or other expert assistance is required, the services of a competent professional should be sought. A catalogue record for this book is available from the British Library. ISBN 978-0-470-74266-2 Set in 9/11 Palatino Roman by Macmillan Publishing Solutions Printed in Great Britain by Bell & Bain, Glasgow
To Carlotta, my gift from God . . . and Josh, Sam, James, Mary, Jonathan, Rachael, Abigail, Davy, and Naomi . . . God’s gifts to us.
About the Author Michael Lively is presently working at the University of Central Florida simulating nano-technology in Papervision3D and CS4. He has worked for 22 years in government and academia and has been programming in Flash and Flex for the past 10 years, building learning management systems. He has worked as the Director of Instructional Design at Northern Kentucky University, Manager of Instructional Design at the University of Cincinnati, Department Chair at Palm Beach Community College in Electronics Engineering Technology, and as a freelance consultant. He holds a BA in Mathematics, BS in Chemistry, BSEE in Electrical Engineering, MS in Physics, and is presently completing his Ph.D. in Quantum Physics.
Acknowledgments It takes many people to publish a book, and first and foremost, I like to thank everyone at Wiley for their support: specifically I would like to thank Chris Webb, Associate Publisher, whose encouragement and professionalism guided me through the entire writing process, and Kenyon Brown, development editor, who kept everything moving on track and in the right direction. Also, a big thanks to Dan Orlando, technical editor of the book who’s technical insight made this book possible. We were very fortunate to have Dan aboard and he played an integral part in the book’s creation. Finally, many thanks to my lovely wife Carlotta who spent many hours helping me proof and edit the book.
Credits Associate Publisher
Associate Production Director
Chris Webb
Martin Tribe
Assistant Editor
Project Coordinator, Cover
Colleen Goldring
David Mayhew
Publishing Assistant
Copy Editor
Ellie Scott
Anne O’Rorke
Development Editor
Compositor
Kenyon Brown
Macmillan Publishing Solutions, Chennai, India
Technical Editor
Proofreader
Dan Orlando
Martin Horsgood
Project Editor
Indexer
Juliet Booker
Jack Lewis – j&j indexing
Production Editor
Cover Image
Claire Spinks
© Punchstock/Score by Aflo
Editorial Director Michelle Leete
Contents Acknowledgments Introduction
viii xxi
Part I: Getting Started Chapter 1: Understanding Flash3D
3
3D Coordinates in Flash 10 Building a 3D Flash 9 Engine in 18 Lines of Code
4 5
Applying Thales Theorem Deriving the Scaling Equation Rendering to the Screen Coding Animation Running the Code Vanishing Point (Asymptotic Zero) Adding a Camera Using CS4 (3D Flash 10 Engine in 13 Lines of Code) Vanishing Point
5 6 8 9 11 12 13 14 14
Using the Big 3 Translation Rotation Scaling (Basic Principles of Animation)
Summary
Chapter 2: Getting Started With Papervision3D Getting Papervision3D Diving into 3D Shapes Versus Objects Hidden Surface Drawing Painter’s Algorithm The View Frustum (Culling and Clipping)
Exploring the Guts of Papervision3D DisplayObject3D Running Papervision3D Four Different Ways (Hello World) Using Flash or Flex
15 15 21 28
32
33 33 34 35 37 37 38
39 41 41 42
Contents Building a Class
43 50
Running Papervision3D Applications
57
Document Class Class Path Flex Builder
Adding Motion Setting the Stage Align Scale Mode Quality
Using BasicView (Hello Plane) Encapsulation Undulating Sphere using BasicView
Incorporating CS4 Summary
Chapter 3: Rezzing Primitives Understanding Primitives Exploring the Guts of a Papervision3D Primitive
Rezzing Primitives to the Stage Prim Template Code (Using a Wireframe Material) Plane Cube Making Ellipsoids
57 58 59
60 65 65 66 66
67 69 69
72 73
75 75 76
79 80 81 83 95
Creating Custom Prims
96
Pinched Sphere Pyramid Geodesic Curved Plane Puckered Cube Torus Tube Hourglass Second Life Tree Double-Sided Plane
96 97 99 100 101 102 105 107 108 110
Creating CS4 Primitives Second Life Tree in Flash CS4 Plane Cube
xii
43
Writing an OOP (Object Oriented) Class Formulating a Papervision3D Class
110 111 114 114
Contents Pyramid CS4 Super Prim (Plane, Cylinder, Cone, Sphere, Half Torus . . . )
Dissecting the Big Three Camera and Viewport
Summary
Chapter 4: Adding Materials Applying Textures Using Simple Materials (or just Materials) Material Types Adding Materials to a Prim Bitmaps Movie Clips Understanding Light Using Shaders Adding Brightness Bump Map
Summary
118 120
125 126
126
127 127 129 130 130 134 142 147 148 160 162
169
Part II: Working with Models and Data Chapter 5: Creating 3D Models Modeling for Papervision3D Bringing Down the Count Gauging Performance in PV3D
Choosing a Modeling Application Blender 3DSMax Google SketchUp Swift3D
Papervision3D Importers What’s MD2? Model Pack Creating MD2 Files (MilkShape to the Rescue) Making Things Move What’s Up with KMZ? Hello Collada Single and Multiple Objects with Simple Images Single Object with Multiple Images Interactive Objects (or Vehicles) and Environments
173 173 174 175
176 177 178 178 179
179 180 181 185 186 187 187 188 191 194
xiii
Contents Animated Objects What’s Up with SketchUp? Binary versus XML
198 200 201
Writing Your Own Parsers
201
Papervision3D and Collada Simple Blender Exporters Simple 3DSMax Exporters Understanding Collada Parsers
202 202 204 204
Processing Images (and Pixel Bender)
206
What’s Pixel Bender? Getting Started with Pixel Bender Writing a Pixel Bender Filter Adding Parameters Typical Point Processes Grayscale Complement Brightness Exposure Color Balance Binary Contrast Simple Pixelate Simple Blur Sharpen Simple Point Light Bloom Brightness Multiple Image Processing CrossFade Subtraction and Addition Bringing Pixel Bender into Flash Adding Pixel Bender to Papervision3D Creating a Multiple Light Source Animating Your Light Sources
206 207 208 210 210 211 211 211 211 211 212 212 212 213 213 214 214 214 214 215 215 217 218
Summary
Chapter 6: Working with Particle Systems
219
221
Particle Systems in PV3D
221
Creating Particles in PV3D Putting Stars in a Skybox
222 223
Creating a Generic CS4 Particle Class
xiv
227
Contents Plug-in Media Flint Particles Viewing Particle Examples Using Flint in Papervision3D
Getting More Bang Out of Flash Summary
Chapter 7: Geocoding, XML, and Databases Geocoding Putting a Google Map on a PV3D Prim Using Air (Flash Sandbox Security Issue) Putting it on the Web with Flex 4 (Gumbo)
XML What Is XML Anyway? Taking a Historic Tour Building a Planetarium in PV3D Building a Flash CS4 Planetarium
Working with MySQL and PHP What’s a Database Anyway? Using Flex with a Database Installing a Test Server Creating a Map Maker Database Automatically Generating PHP
Summary
Chapter 8: Gliding on AIR Building a Simple Modeling Program Placing Particles at Vertices Modeling Terrains
Creating a Terrain Editor in AIR
244 249 249 249
251 251
253 253 254 255 259
262 263 265 268 273
275 275 276 276 277 278
284
285 285 286 287
293
Drawing Code Using Radio Buttons Adding Sliders Using Buttons Working with Perlin Noise Adding Key listeners
294 295 296 296 298 299
Incorporating Adobe AIR
306
Accessing your Local File System Saving BitmapData
307 307
xv
Contents Saving to Your Local PC Using Flash 10 Turning Your Webcam into a PV3D Heightmap Using SQLite Deploying AIR
Sculpting a Prim Using face3d Summary
310 311 312 312
313 317
Part III: Building Games and Websites Chapter 9: Incorporating 3D Physics
321
Understanding the Guts of Physics
321
Adding Physics to Your Camera Creating Camera Perspectives Cruising the Stars Using Cameras Adding Physics to the DisplayObject3D Class Building Your Own Physics Classes Simple Pendulums: Line, Ball, Animation Double Pendulum: What’s pinning? Torsion Pendulum: Cylinder on a Rod Foucault Pendulum: A Little More Realism
Interacting with Objects in PV3D Building an Interactive Cube: Face Level Browser Double-Click Problems Building Interactive Particles: Object Level Rotating Small Sphere Clicking on Particles Rolling Over the x, y, z Gizmo
Taking it to the Next Level: 3D Wall Course
322 323 324 326 328 329 330 331 331
332 332 337 338 338 339 340
341
The Big Problem Rapid Large-Scale Development
344 345
Using 3rd Party Physics Engines
346
WOW Physics Jiglibflash Implementing Jiglibflash Building an Example Viewer
AS3Dmod The Heart of AS3Dmod Making National Treasure: Porting
Summary
xvi
347 348 348 356
360 361 361
363
Contents Chapter 10: Building 3D Games for Wii Shooting Pool in Space Five Game States: Start, Play, Win, Lose, Cancel What’s Modal Control? Adding a Background Image Using View States Building a Custom Skybox Building a Jiglib Bounding Box Creating Your Game Elements Following Game Flow
Optimizing Your Game for the Web Using Your Wii Wii Starter Code Drawing with Silly String Adding Wii to Your Game
Playing Wii Jiglib Pong Building the Bounding Box Adding Jiglib Moving at Constant Velocity Adding AI Making Game Levels and a Scoring System Creating Textboxes Adding Reflectivity and a SkyBox Building a Wii Navigation System Adding Mouse-Controlled Paddle Motion
Summary
Chapter 11: Integrating the Flash Media Server Getting Started Configuring Your Development Environment Connecting to the FMS The Big Mistake Checking on Your Program’s Performance Running a RSO Hello World Dragging Objects
Building a Race Track Creating a Track Adding the FMS to Your Slot Car Turning Your Code into a Game
365 366 367 367 368 368 373 375 377 386
390 393 393 397 402
404 406 406 408 408 408 410 411 411 414
415
417 417 419 419 421 422 423 436
437 438 445 456
xvii
Contents FMS Alternatives Red 5 Wowza Adobe Flash Collaboration Service
Summary
Chapter 12: Developing 3D Websites
456 457 457 457
458
459
What You Need to Know
459
Why Use 3D Building it Right in Flash Learning Photoshop Creating a Site Map
459 460 461 461
Converting CSIS to 3D Making it 3D Using Reflection Making your Collage Making Clouds Making Trees Placing your Panels with XML Building a Nav System Hey! Where’s the Preloader?
Flirting with Web Spiders Using HTML Using SWFObject
Summary
462 463 464 465 466 468 470 474 476
483 484 485
488
Part IV: Extending PV3D and Beyond Chapter 13: Making 3D Movies Houston . . . We’ve Got a Problem Using Flash CS4 Rebuilding CSIS Seven Revolutions in Flash CS4 Adding a Preloader
Bridging the Gap with Flash Catalyst Laying it out in Photoshop Creating Code Automatically in Flash Catalyst Sending Your Work to Flash Builder
xviii
491 491 492 492 499
500 500 501 503
Contents Pushing the Envelope with Flash Builder Birthing a Spark Animating in 3D Using Flash Builder
Going 3D with Photoshop Getting Started Tips Unlocking the Power of Google 3D Warehouse
Recording 3D Animations Creating an Animation Recorder in PV3D Storing Your Results Using the DOM Animating Spark
Building a PV3D Studio VizualPV3D
Summary
Chapter 14: Taking Virtual Tours Hello “Uncle Sam” Using Drones – the Yang of VR
Creating Augmented Reality Playing a Game in Augmented Reality
Building 3D Worlds Summary
Chapter 15: Adding Services Tweeting Creating Your Layout in Photoshop Adding Interactivity in Flash Catalyst Building Connectivity in Flash Builder
503 504 505
511 511 511
512 512 515 516
518 518
519
521 521 522
523 527
550 551
553 553 554 554 559
Adding 3D to Your Flickr Menu
565
Using Photoshop Adding 3D in Flash Catalyst Hooking it up in Flash Builder
565 566 566
Mining Yahoo’s Treasure Trove
570
Checking Your Local Weather
571
Building a CNN News Feed
577
Connecting Your Service Binding Your Data Configuring Your Data
577 578 578
Summary
583
xix
Contents Chapter 16: Flash & Math: Exploring Flash 10 and Beyond
585
Rebuilding the 3D Pool “Shooting”
586
Laying out Your Game’s Framework Adding Code in Flash Builder Optimizing PV3D for Flash 10
586 589 590
Rendering 3D Models in Flash 10
591
Creating a 3D Image Ball Using drawTriangles to Build a Shaded TIE Fighter An Orbiting Ship using drawTriangles Taking drawTriangles to the Next Level Building a Google Wormhole Creating a Molecule Viewer Mapping a Single Image onto an Octahedron
Using ILayoutElement Summary
591 594 599 606 606 610 617
620 621
Appendix A: Pocket Reference
623
Appendix B: Companion Website
693
Index
697
xx
Introduction Flash programmers have always enjoyed a freedom of expression unparalleled in other programming platforms. And with the release of AS3 and CS4, Adobe has propelled that freedom of expression into the third dimension. But long before AS3, Flash developers were experimenting with 3D. And applications like Papervision3D formalized these endeavors into a robust object-oriented class structure. The acceptance and popularity of Papervision3D has become a driver of change in the Flash developer community. Originally conceived by Carlos Ulloa in November 2005 and converted to Great White by Ralph Hauwert, Papervision 3D has grown from approximately 20 to over 300 classes. It’s maintained by a 10+ member core and committer team. And with the release of Papervision3D, 2.0, developers worldwide have been contributing to this phenomenal open source project. But what’s so special about 3D? Why should you even care? Besides the “wow!” factor, 3D has the highest learning impact on your brain: driven by the most efficient information processor in your body – your visual cortex. If you want to inject information into your client rapidly – do it visually! It’s not just visual stimulation that creates a good learning environment; it’s also tapping into your client’s emotions. This is the essence of brain-based learning; an emotionally driven 3D visual experience. That’s why 3D RPGs (role-playing games), like Second Life, have so much popularity on the web. They’ve tapped into their client’s emotionally driven visual cortex. Creating complete web experiences like the one described previously required extending Papervision3D beyond its present framework. And one of the purposes of Professional Papervision3D is to provide you with the necessary tools to do so. Flash 3D coding has come a long way since the early days of Flash. And the 3D coding architectures of the future will not only be fast (incorporating such technologies as Pixel Bender and Alchemy), but also self-generating. Full 3D websites including 3D models will be created using graphical methods very similar to those of Flash Catalyst. This book and its website bridge the gap between the old and the new, showing how Papervision3D’s solid OOP (object oriented programming) structure can be extended to add additional features, and looking into the next generation of 3D architecture, which incorporates auto-code generation using tools such as Flash Catalyst and Flash Builder.
Who This Book Is For Professional Papervision3D and its companion website have been specifically written for beginning and intermediate developers. It’s been created for those who are seeking both basic knowledge and the tools required to extend Papervision3D beyond its present capabilities.
Introduction An emphasis has been placed on learning OOP throughout the book. And various class packages are built and the principles behind their construction explained. In addition, much starter code has been created that can be used for more advanced projects. Many basic concepts are explained in simple understandable terms and illustrated through numerous code examples. In addition, each major topic in the book has a video associated with it found on the book’s website. This approach is designed to maximize your grasp of the content and convey concepts difficult to explain in text. Finally, a number of supporting topics such as the use of Photoshop, 3DSMax, and Blender are covered on the book’s website as well. If you’re trying to learn PV3D for the first time, need to master OOP, want to learn PV3D supporting technologies or to extend PV3D to do something that’s not currently possible – this is the book for you!
What This Book Covers The book covers everything that you need to know to get started in Papervision3D, and how to extend Papervision3D and create your own Papervision3D classes. It covers how to work with 3D models and add data connectivity to your programs. It covers how to build Wii games and 3D websites. And shows you how to extend what you’ve learned to build 3D models in CS4.
How This Book Is Structured The chapters in Professional Papervision3D are divided into four major parts: ❑
Getting Started
❑
Working with Models and Data
❑
Building Games and Websites
❑
Extending PV3D and Beyond
Each part has four chapters and each chapter builds on the next with an emphasis on digging into PV3D’s classes, understanding important OOP principles, and creating your own supporting 3D classes. Each part (and their chapters) is described below.
Part I: Getting Started In Part I, you learn all the basics to get you up to speed in using PV3D. You learn how to build 3D engines, how to download and get PV3D running, how to build classes, and how to work with primitives and materials. Finally, you extend these concepts to build CS4 primitives with materials. Chapter 1, “Understanding Flash3D” — Flash 3D is one of the fastest-moving areas in web technology. In this chapter, you learn how to build a simple 3D engine in both CS3 and CS4. Using what you learn
xxii
Introduction about 3D engines you create a torus worm, carousel, and image ball. Finally, you examine Disney’s rules for creating realistic animation, and convert a timeline animation into ActionScript. Chapter 2, “Getting Started With Papervision3D” — In this chapter, you’re introduced to the basics of Papervision3d, Painter ’s Algorithm, and the view frustum. You learn about culling and clipping and examine the guts of Papervision3d. You instantiate your first primitive (a sphere) and add a wireframe material. You extend your application using BasicView, and examine the different ways to run an application. Chapter 3, “Rezzing Primitives” — In this pivotal chapter, you examine how prims are made in both Papervision3D and CS4. You create your own custom prim by examining the parametric equations of different prims. And in the case of creating a Second Life tree, you analyze the potential savings between using Papervision3D and CS4. You learn about the powerful new classes and methods in the CS4 library (such as Matrix3D, drawTriangles, and vectors). And learn to create your own CS4 super prim, using a switch case and timer. Chapter 4, “Adding Materials” — In this chapter you turn the corner from 3D techno-babble to application building. You learn the basics of how materials are used to create objects and about their lighting. You learn how to add brightness to a Papervision3D light source, create shades, and make bump maps. And you extend these concepts to CS4.
Part II: Working with Models and Data In Part II, you learn how to create your own 3D models and import then into PV3D. You learn to create particle systems, bring Google Maps into PV3D and CS4, and build data-driven applications in Flash, Flex, and Air. Chapter 5, “Creating 3D Models — In this chapter, you start with modeling and end up with Pixel Bender. Regardless of how PV3D changes over time the principles presented in this chapter will be around for a while. You’ll still need parsers to bring in vertex data regardless of your software environment. And Pixel Bender, the new kid on the block, will obviously become the cornerstone of any new 3D package hitting the scene. Chapter 6, “Working with Particle Systems” — This chapter gives a broad overview of particles in both PV3D and CS4. You start with the PV3D particle system and add some of your own particles to it and create a starry panorama. You build a 3D particle system from scratch and create a Flash CS4 glowworm. You learn how to slice and explode particle systems, and how to use them to interact with video. You take a look at the great work that Plug-in Media is doing and learn how to incorporate FLINT into PV3D. Chapter 7, “Geocoding, XML, and Databases” — In this chapter, you turn the corner from learning the inner workings of PV3D to using it to build data-driven web applications. You build a number of applications in Air, Flash CS4, and Flex, which illustrate the use of XML, PHP, and MySQL. You learn how to use the Flex data wizard to automatically create PHP code, which is used to make server requests. Chapter 8, “Gliding on AIR” — In this chapter, you build your first editor in Adobe Air. During the development process you master the use of a few basic Flex components that you’ll use again to create other editors. Accessing your local PC file system using Air and Flash10, you save your editor results to
xxiii
Introduction your local hard drive. You learn about creating grayscale height maps and using the geometry.vertices property to bring those maps into PV3D. Using this you create a PV3D terrain viewer to view your heightmaps. Extending your editor you capture your webcam, programmatically change it into grayscale, and bring it into your PV3D terrain viewer.
Part III: Building Games and Websites In Part III you explore bringing physics into PV3D by creating custom physics classes, you modify the DisplayObject3D class, and you learn to use Jiglib. You build Wii controlled games, investigate creating multiplayer games using the Flash Media Server, and create a 3D website. Chapter 9, “Incorporating 3D Physics” — In this chapter, you examine a number of approaches to bring physics into PV3D. You start by creating a spring camera, and beefing up the DisplayObject3D class to add gravity effects, to create orbiting planets. You create custom classes for oscillation and learn how to make particles interactive using the interactive scene manager (or ISM). You learn how to build largescale applications using states, modules, and porting. You examine both WOW and Jiglibflash physics engines. Finally, you build a Jiglib Hello World example and a Jiglib example viewer. Chapter 10, “Building 3D Games for Wii” — Building games is great fun and in this chapter you learn the basics of building game systems, which include game states and multi-levels. This chapter explores two different games: pool “shooting” and Pong. In the pool game you learn to build an entire game system based on Flex View States. And in the Pong game you learn to build a multi-level Wii controlled game. Both games are built in Flex, but the Pong game can easily be built in Flash, as it’s an ActionScript package. You build bounding boxes for your games by hacking the Jiglib PV3D plugin class and create a skybox. Chapter 11, “Integrating the Flash Media Server” — Creating Rich Internet Applications has long been the goal of Macromedia (now Adobe). An integral part of that has been using the Flash Media Server to create interactive web experiences. In this chapter, you learn how to get your users interacting with 3D objects using remote shared objects. You also create the starter code for a slot car racing game. Finally, you examine alternatives to the FMS such as Red 5, Wowza, and the Flash Collaboration Service. Chapter 12, “Developing 3D Websites” — In this chapter you convert the CSIS 2D site to a 3D site. You create custom tree, cloud, and collage classes and build a 3D navigation system from scratch using PV3D components. You examine a few important reality checks when it comes to building websites: using a design doc, learning Photoshop, and combining 2D with 3D to get an optimized 3D experience. Finally, you learn how to optimize your website for search engines by adding html text to your html-swf wrapper file and by using the SWFObject.
Part IV: Extending PV3D and Beyond In Part IV, you learn to take PV3D to the next level of performance by using Flash CS4, Photoshop3D, Flash Catalyst, and Flash Builder. You add augmented reality, services, and examine a number of CS4 rendering engines. Chapter 13, “Making 3D Movies” — This chapter highlights one of the greatest challenges developers face today – the rapid change in programming architecture and platforms. This is demonstrated by building the Seven Revolutions project from the previous chapter in Flash CS4. Next, you examine Flash Catalyst and its integration into Flash Builder. Finally, the new Animate Super Effects class is used to
xxiv
Introduction create a panel animation, Photoshop3D is examined, and a PV3D animation engine that saves its results to the DOM is developed. Chapter 14, “Taking Virtual Tours” — As technology advances, Virtual Reality is playing a key role. In this chapter you examine using VR in the military, using augmented reality, and building 3D worlds. The majority of the chapter treats augmented reality using the FLARToolkit, which was created by Saqoosha. Saqoosha’s starter kit code is examined and extended to create PV3D BasicView starter code. Using this starter code a number of items are imported in FLAR including the Jiglib pong game created in the chapter on games. Chapter 15, “Adding Services” — In this chapter you learn to hook up Web Services using Flash Catalyst and Flash Builder. You build a Twitter viewer, Weather checker, Flickr picker, and CNN news reader. Flash Builder has a full range of data connectivity options . . . WSDL, PHP, Coldfusion, BlazeDS, and LCDS. This makes data connectivity easier and gives you more time for creative development. Chapter 16, “Flash & Math: Exploring Flash 10 and Beyond” — Flash 3D coding has come a long way, and over the next few years will transcend even optimistic expectations. In this chapter you scratch the surface of such technologies by rebuilding your 3D pool “shooting” game in Flash Catalyst, and visiting a number of CS4 rendering examples.
Appendices The book has two appendices: Appendix A is a pocket reference that contains all the important formulas and code snippets from each chapter. It purpose is for quick reference when programming applications that may use similar code snippets. Appendix B describes the book’s companion website in detail, describing its purpose and various training courses designed to help convey the book’s material and the use of supporting technologies.
What You Need to Use This Book Professional Papervision3D starts by emphasizing the use of Flash and moves to Flex 3 (Gumbo), Air, Flash Catalyst and Flash Builder. In addition, Papervision3D uses a number of supporting technologies such as Blender, Photoshop, 3DSMax, and so on. The applications that you need to complete the exercises in this book fully are Flash CS4, Photoshop, Pixel Bender, Blender (or 3DSMax), Flash Catalyst, MilkShape, and Flash Builder. To help you master a number of these supporting technologies the book’s website has the following video tutorials to help you get started: ❑
Photoshop
❑
Illustrator (Basics)
❑
3DS Max
xxv
Introduction ❑
Blender
❑
SketchUp
❑
Flash CS4
❑
Flash Catalyst
❑
Flash Builder (Gumbo)
If you’re on a budget, Blender is free (at www.blender.org) and the Adobe products can be downloaded for full 30 days trials from www.adobe.com, the Eclipse + Flex SDK version is free, and Pixel Bender is free at http://labs.adobe.com/technologies/pixelbender/.
The Book ’s Companion Website The book’s companion website (at www.professionalpapervision.com) is an integral part of conveying the book’s contents. The days when code was small and easily explained in a few written pages are gone. Today, many Flash applications are thousands of lines of code with large supporting libraries. Using video to help convey the concepts behind such applications is the primary goal of the book’s website. You’ll find a large set of video resources on the book’s website designed to help you come up to speed on the book’s topics. The website consists of: ❑
Book chapter videos on each major topic
❑
Training videos on topics supporting PV3D
❑
Chapter code for download and viewing
❑
Additional topics from the book
❑
Bonus examples and new Flash 3D technologies
❑
Current 3D topics from UCF
Using the book and the book’s training videos together should help you come up to speed rapidly in Flash 3D and its supporting technologies.
How to Use This Book Each major topic in the book has a video associated with it. This approach is designed to maximize your grasp of the content and convey concepts difficult to explain in text. In some cases, portions of the book were written to go hand-in-hand with its counterpart video content. To maximize your absorption of the contents, first read a section in the book, watch its video, try the code examples, and then try extending the code’s capability.
xxvi
Introduction
Conventions To help you get the most from the text and keep track of what’s happening, we’ve used a number of conventions throughout the book: ❑
We show file names, URLs, and code within the text like so: persistence.properties.
❑
We present code in two different ways:
In code examples we highlight new and important code with a gray background. The gray highlighting is not used for code that’s less important in the present context, or has been shown before.
Source Code Many Flash applications are thousands of lines of code and have large supporting libraries. Due to the size of many 3D applications you no longer have the luxury of showing every line of code that goes behind each application. And in many instances, in this book, after a discussion of how code works the reader is sent to the book’s website to download the entire application for review. The amount of code written for the book was significant (about 119,000 files), and as the book was written the versions of PV3D and other supporting software changed. So as opposed to trying to keep track of every different version of PV3D, the version of PV3D (and other supporting software) used to create each application was included with each application. All of the source code used in this book is available for download at http://www.wrox.com. Once at the site, simply locate the book’s title (either by using the Search box or by using one of the title lists) and click the Download Code link on the book’s detail page to obtain all the source code for the book. Because many books have similar titles, you may find it easiest to search by ISBN; this book’s ISBN is 978-0-470-74266-2. After you download the code, just decompress it with your favorite compression tool. Alternately, you can go to the main Wrox code download page at http://www.wrox.com/dynamic/books/download .aspx to see the code available for this book and all other Wrox books.
Errata We make every effort to ensure that there are no errors in the text or in the code. However, no one is perfect, and mistakes do occur. If you find an error in one of our books, like a spelling mistake or faulty piece of code, we would be very grateful for your feedback. By sending in errata, you may save other readers hours of frustration and at the same time you’ll be helping us provide even higher quality information. To find the errata page for this book, go to http://www.wrox.com and locate the title using the Search box or one of the title lists. Then, on the book details page, click the Book Errata link. On this page you can view all errata that has been submitted for this book and posted by Wrox editors. A complete book
xxvii
Introduction list including links to each book’s errata is also available at http://www.wrox.com/misc-pages/ booklist.shtml.
If you don’t spot “your” error on the Book Errata page, go to http://www.wrox.com/contact/ techsupport.shtml and complete the form there to send us the error you’ve found. We’ll check the information and, if appropriate, post a message to the book’s errata page and fix the problem in subsequent editions of the book.
p2p.wrox.com For author and peer discussion, join the P2P forums at http://p2p.wrox.com. The forums comprise a web-based system for you to post messages relating to Wrox books and related technologies and to interact with other readers and technology users. The forums offer a subscription feature to email you topics of interest of your choosing when new posts are made to the forums. Wrox authors, editors, other industry experts, and your fellow readers are present on these forums. At http://p2p.wrox.com you’ll find a number of different forums that’ll help you not only as you read this book, but also as you develop your own applications. To join the forums, just follow these steps:
1. 2. 3.
Go to http://p2p.wrox.com and click the Register link.
4.
You will receive an email with information describing how to verify your account and complete the joining process.
Read the terms of use and click Agree. Complete the required information to join as well as any optional information you wish to provide and click Submit.
You can read messages in the forums without joining P2P, but in order to post your own messages, you must join. Once you join, you can post new messages and respond to messages posted by other users. You can read messages at any time on the web. If you would like to have new messages from a particular forum emailed to you, click the Subscribe to this Forum icon by the forum name in the forum listing. For more information about how to use the Wrox P2P, be sure to read the P2P FAQs for answers to questions about how the forum software works, as well as many common questions specific to P2P and Wrox books. To read the FAQs, click the FAQ link on any P2P page.
xxviii
Par t I: Getting Star ted Chapter 1: Understanding Flash3D
Part XX
Chapter 2: Getting Started With Papervision3D
Par t Title
Chapter 3: Rezzing Primitives Chapter 4: Adding Materials
Part 1: Getting Started In Part I, you learn all the basics to get you up to speed for using Papervision3D (PV 3D). You learn how to build 3D engines, download and get PV 3D running, build classes, and work with primitives and materials. Finally, you extend these concepts to build CS4 primitives with materials.
2
Understanding Flash3D Flash programmers have always enjoyed a freedom of expression unparalleled by other programming platforms. And with the release of CS4, Adobe has propelled that freedom of expression into the 3rd dimension. But 3D didn’t start with AS3. Flash developers were experimenting with 3D long before. And applications like Papervision3D formalized these endeavors into a robust object-oriented class structure. The acceptance and popularity of Papervision3D has become a driver of change in the Flash developer community. But underneath the 300 classes that make up Papervision3D still beats the original algorithms used by the early developers of Flash 3D. Understanding how to create 3D in Flash is essential to fully grasping Papervision3D and applications like it. As you learn how 3D engines were originally constructed, you’ll gain both an insight into Papervision3D’s core architecture and an appreciation of its robust structure. Its complexity will fade into a set of fundamental 3D algorithms. Papervision3D, at its core, is a perspective projection engine, where projection simply means transforming a 3D “object” space into 2D Flash x and y screen space. And surprisingly, the whole process hinges on one equation: a perspective scaling equation derived from Thales Theorem: T = scale = focal length/(focal length + z)
In the equation above, T is the perspective scale, and z is the distance from the projection plane. The focal length (or calculated “screen” location) determines the amount of perspective provided in the view. You use this equation to create an illusion of depth by scaling 2D surfaces. In this chapter, you use Thales Theorem to create a simple 3D engine in just 19 lines of code. Then using Flash CS4 you rebuild your engine in just 13 lines of code. You cover the big 3: translation, rotation and scaling. And applying what you’ve learned you create a 3D torus worm, carousel, and image ball. Finally, you cover the basics of making believable animation, and how to turn a timeline animation into pure ActionScript.
Part 1: Getting Started But before you get started it’s important that you understand the coordinate system you’ll be rendering your objects in. It’s a little different to the way you learned it in math class, and well worth reviewing.
3D Coordinates in Flash 10 Understanding the Flash coordinate system is vital to rendering in Papervision3D or CS4. With the transition to Flash Player 10, every ActionScript display object has a z property. Adding a 3rd dimension allows an object to move towards and away from the viewpoint of the user using perspective scaling. As a side note, this extra dimension is easily handled using a 3D transformation matrix that incorporates the three fundamental transformations found in all physical systems, such as translation, rotation, and scaling. Every 3D object within Papervision3D has a Matrix3D. The Matrix3D class supports complex transformations of 3D geometry, such as rotation, scaling, and translation. But unlike other 3D systems, especially those you might have met in math class, the y and z-axes are reversed as shown in Figure 1.1.
Y Z
X
X
Z Y
Figure 1-1
From the figure you can see that:
4
❑
x increases as you move to the right along the x-axis
❑
y increases as you move down along the y-axis
❑
z increases as you move away from the viewpoint.
Chapter 1: Understanding Flash3D In addition, in Flash 9 the coordinate origin (0,0) is not located at the center of the screen, but at the upper left corner of your screen. This becomes an issue when working with the asymptotic vanishing point, and you’ll learn how to adjust for this later in this chapter.
Building a 3D Flash 9 Engine in 18 Lines of Code In this example, you’re going to build a 3D engine based on perspective scaling in just 19 lines of code. Before Flash 10 there was no native support for 3D and all you had were x and y coordinates to play around with. So you needed to add another dimension to get 3D (a z-axis). The trick to creating a z-axis was to use perspective scaling. Which means that as an object moves away from you it gets smaller and as it moves towards you it gets larger. But we need to quantify this idea (make some math out of it) so we can program it. And that come from Thales Theorem.
Applying Thales Theorem The Greeks are famous for their geometry and Thales (a Greek Philosopher) was the first to propose similar triangles. From the concept of similar triangles you get simple linear projection: which is the heart of a Flash 3D engine. Of course perspective drawing really didn’t take off until the Renaissance, and now again in Flash 3D. Imagine that you’re in your home looking out the window. As you approach the window objects outside look larger and as you get farther away from the window, objects outside look smaller. Your distance from the window is called your focal length, the window is your projection plane (or viewport), and your eye is the vanishing point. Now remain stationary, this fixes your focal length; watch outside as objects move closer to and farther away from your window. As a bird flies closer to the window it looks larger and as it flies away it looks smaller. This is your z-axis: the distance between the outside object and your windowpane. The equation that governs this behavior is: T= scale = focal length/(focal length + z)
where T equals “one” when the outside object is at the window and “zero” when your object (bird) flies far away (off to infinity, also called vanishing point). This equation works well for Flash and is illustrated in the graphic below as a Blender monkey peers at a projection plane. Focal length is the distance from the vanishing point (monkey’s eye) to the projection plane (see Figure 1.2).
5
Part 1: Getting Started
Figure 1-2
Creating the illusion of depth (or 3D) using perspective scaling is sometimes referred to as 2.5D. Another term used to describe perspective projection is 2.5D. The term is usually used with computer graphics, especially video games, where a computer system uses 2D computer graphics to visually simulate 3D computer graphics. In Flash, you use the z perspective scale to create a perspective projection onto the Flash x, y screen.
Deriving the Scaling Equation You’re probably wondering how the scaling equation presented above was derived. As mentioned earlier, it came from Thales’ idea of similar triangles. The figure below shows two nested (or similar) triangles. Small h is the size of an object at the computer screen and large H is the actual size of the object beyond the screen. And as described earlier fl (or focal length) is the distance from your computer screen to your eye, and z is the distance on the other side of your screen to your object as shown in Figure 1.3.
Computer Screen
H
h fl Vanishing Point
Figure 1-3
6
z
Chapter 1: Understanding Flash3D From the law of similar triangles, it follows that h H fl fl z
By cross-multiplying, you get the scaling equation: Scale
fl h fl z H
That’s all there is to its derivation, but examining the equation reveals an interesting phenomenon: a singularity (blow up) in z. As z approaches –fl your equation’s denominator goes to zero and your scale blows up to infinity. And as shown in the figure below after you pass –fl your figure goes through an inversion (it flips over). You’re probably wondering how fl (focal length) is assigned. Is it actually the distance of your eye to the computer screen? No! It’s a virtual quantity that was used in the derivation of the scaling equation. But that doesn’t mean it doesn’t have meaning. It is the focal length of your camera, and its assignment changes the appearance of what you see on your computer screen. The figure below illustrates a number of important occurrences in the life of the scaling equation: ❑
When z = 0 your image scale is 1 (image is its actual size)
❑
When z = fl your image scale is ½ (image is half its size)
❑
As z approachesfl your scale approaches infinity (image is infinitely large)
❑
As z approaches infinity your scale approaches zero (image has vanished)
Of the four conditions listed above, two are used most by mathematicians to quickly define the behavior of a function, the singularity (blow up point) and asymptotic behavior (as z approaching infinity). The asymptotic behavior is important here since as your object moves towards infinity it vanishes. This is referred to as the vanishing point and occurs at a specific x, y position of your screen. In Flash, that position is at the origin that occurs at the upper left corner of your computer screen. Later in this chapter you’ll find out how to change your vanishing point. This may cause you a little confusion since you’ve already learned that the vanishing point occurs at the eye as shown in Figure 1.2 previously. So are there two vanishing points? Definitely, but the first one that occurred at the eye was used for derivation purposes and is where your singularity occurs (shown in Figure 1.4). The second vanishing point occurs at infinity. It’s the one that you’ll now be most interested in working with. It determines where your 3D object goes (on the x,y screen) as it gets further away from you.
7
Part 1: Getting Started
scale
as z > fl, scale > infinity
1 as z > infinity, scale > 0
1/2
Inversion
z fl
z 0 z fl
z
z fl is a singularity at vanishing point
Figure 1-4
The curve above is very similar to the gravitational or electrical potential curves found in physics. And it further suggests that a size scaling force field could be constructed by taking the differential of the scaling equation. But that’s beyond the scope of this book.
Rendering to the Screen Using perspective scale you’ve picked up a 3rd dimension: z. But in order to render it to the Flash Stage you’ve got to get rid of it again. The process is called rendering and occurs by projecting a 3D object (created from perspective) to a 2D computer screen (or viewport). Projection onto the Flash screen requires that you convert a 3D point (x, y, z) into a 2D (x, y) point. You accomplish this by using scaling to drop the z coordinate. A change in z affects the position and size of an object in 3D space as shown in the code below: scale=focal length/(focal length + z); x=x*scale; y=y*scale; xscale=yscale=scale*100.
In the code, the scale (based on focal length and z) changes the x and y position and the x and y scale, resulting in a 2.5D effect described earlier. And it’s done the same way in Papervision3D. Throughout this book, you’ll be drilling down into Papervision3D’s classes and examining how it does what it does. Below is a code snippet taken from its Camera3D class.
8
Chapter 1: Understanding Flash3D In Chapter 2, you’ll find out how to obtain the Papervision3D classes and how to drill down and examine its code. This particular code snippet is found in the org/papervision3d/camera folder. if(screen.visible = ( focus + s_z > 0 )) { s_x = vx * m11 + vy * m12 + vz * m13 + view.n14; s_y = vx * m21 + vy * m22 + vz * m23 + view.n24; //perspective scaling in Papervision persp = fz / (focus + s_z); screen.x = s_x * persp; screen.y = s_y * persp; screen.z = s_z. }
The code snippet above taken from Papervision3D’s Camera3D class demonstrates how Papervision3D uses perspective scaling. You’ll use this idea to create a 3D Flash engine in just 19 lines of code.
Coding Animation Okay, you should now understand enough about perspective scaling to begin coding your 3D engine. In this example, you create your code in the Flash API, but many Papervsion3D developers use either Flex or Eclipse for their code development work. Flex and Eclipse offer superior code debugging and code hinting capabilities over Flash, and in many instances their use speeds your code development by a factor of 4. It’s worth trying one of them out. In this exercise, you’ll create a circle that oscillates in and out of the screen in the z perspective direction. Whether using the timeline or pure ActionScript, Flash in essence is an animation machine. When using purely scripted animation, the animation process is based upon a looping (or updating) procedure shown in Figure 1.5.
Grab Imports Set Initial Parameters Create Graphical Elements
Listener
Render Frame
Display Frame
Alter Frame
Figure 1-5
9
Part 1: Getting Started The typical flow of such a program involves first initializing the program and then using a listener to loop through a scripted animation routine. The whole process can be divided into three parts: initialize, listen and loop:
Initialize: Steps 1.3 Initializing an AS3 program can be a little overwhelming at first. The question that most people ask is how do I know what imports to bring in? There is no magical pool of knowledge that can help you. It just takes experience (or reading the documentation, or paying attention to syntax). As you work with the different Flash and Papervision3D methods you’ll become experienced in spotting classes that need to be imported. For example, if you’re creating a Wii game, it’s obvious that you’ll need to import some Wii classes. But, which ones? It depends on which methods you’re using to create your program. From experience, you’ll begin to recognize and associate the right classes with their methods. Or better yet, Flex builder has an auto-complete that automatically adds the classes you need upon auto-completion of a method. In the example below, you create a ball sprite, so it makes sense that you’ll need to import the Sprite class. After importing the Sprite class, you initialize the position and angle of the ball and then set your focal length (from experience 300 works well for this example). Then in step 3, you create your ball, give it a color and set it on the stage using the addChild method.
1.
Start by importing the Sprite class; this is where you’re going to draw a ball that you will animate in 3D.
import flash.display.Sprite;//imports sprite class
2.
Next declare your variable’s zposition, angle, and focal length.
var zposition:Number = 0;//z position var myAngle:Number =0;//Angle of ball var fl:Number = 300; //focal length
3.
Next create your ball and add it to the stage.
var ball:Sprite = new Sprite();//instantiates ball sprite ball.graphics.beginFill(0xFF0000);//Assigns a ball color ball.graphics.drawCircle(0, 0, 40);//draws your ball at (0,0) ball.graphics.endFill();//ends the fill addChild(ball);//adds the ball to the stage
Listen: Step 4 In AS3, the event architecture is bigger, better, and badder (in a good way). Written from the ground up, it’s fast, powerful, and easy to use. It incorporates listener objects to listen for events. Since event listeners all work the same way, once you understand how to use one of them you understand them all. This is how they work:
10
Chapter 1: Understanding Flash3D ❑
Call the method addEventListener to listen for an event
❑
Name the event you want to listen for
❑
Name the function you want to execute when the event occurs
So in the code below, you want to iterate your animation periodically. This can be done using the enter frame event listener or a timer. In this case, you’ll listen for the enter frame event. Each time a frame event occurs the function onEnterFrame is called, as shown in step 4. ❑
Next create your onEnterFrame listener which loops through your equations of motion. This is the heart of all 3D engines. addEventListener(Event.ENTER_FRAME, onEnterFrame);//loops equations
Loop: Step 5 Looping is the heart of creating a scripted animation. You’re probably familiar with creating frame loops on Flash’s timeline. But in Papervision3D, you don’t use the timeline. Everything is done in ActionScript. But you can still create graphical animation by creating a frame ripper, or use animation script created in Flash by grabbing the animation elements as classes. In Step 5, the animation is created by incrementing the angle on each loop. As the angle is incremented the ball oscillates and the z position changes. ❑
Create the function that will be looped. These are your equations of motion that govern the perspective as it is changed and converts 3D to 2D (or projects onto the viewport). function onEnterFrame(event:Event):void{ var scale:Number = fl / (fl + zposition);//scale perspective myAngle=myAngle+.1;//iterates angle myB all.x = 300*Math.sin(myAngle)*scale; //ball orbit x ball.y = 300*Math.cos(myAngle)*scale; //ball orbit y ball.scaleX = scale;//scales perspective in x ball.scaleY = scale;//scales perspective in y zposition = 2000*Math.sin(myAngle/10);} //increments z and changes sign.
Upon every loop, the myAngle variable is iterated and the ball.x and ball.y positions oscillate sinusoidally. This creates the circling motion of the ball. In the last line of code the zposition variable oscillates sinusoidally as well, causing the ball to cycle in and out of the screen. By putting these two motions together your ball will spiral in and out of the screen in the z direction. You now put it all together and run the code.
Running the Code The code (all 18 lines of it) is listed below. Just type it in the Flash ActionScript editor and click control test. You’ll see a red ball oscillating on the z-perspective axis. Not very exciting, but there are a number of concepts here that you’ll use throughout the book.
11
Part 1: Getting Started import flash.display.Sprite;//imports sprite class var zposition:Number = 0;//z position var myAngle:Number =0;//Angle of ball var fl:Number = 300; //focal length var ball:Sprite = new Sprite();//instantiates ball sprite ball.graphics.beginFill(0xFF0000);//Assign a ball color ball.graphics.drawCircle(0, 0, 40);//draws your ball at 0,0 ball.graphics.endFill();//ends the fill addChild(ball);//adds the ball to the stage addEventListener(Event.ENTER_FRAME, onEnterFrame);//loops equations function onEnterFrame(event:Event):void{ var scale:Number = fl / (fl + zposition);//scale perspective myAngle=myAngle+.1;//iterates angle myB all.x = 300*Math.sin(myAngle)*scale; //ball orbit x ball.y = 300*Math.cos(myAngle)*scale; //ball orbit y ball.scaleX = scale;//scales perspective in x ball.scaleY = scale;//scales perspective in y zposition = 2000*Math.sin(myAngle/10);} //increments z and changes sign
The code, though it’s not Papervision3D, illustrates a number of important concepts that every 3D engine possesses: ❑
A frame looper/renderer
❑
Perspective (z-coordinate)
❑
Projection onto a viewport
❑
Primitive or basic shape
❑
Addition of a color (or material)
And all of this is done in just 19 lines of code. If only it had stayed this simple. Papervision3D started off with only 20 classes, now it’s in the hundreds and growing. But as they say, no pain no gain.
Vanishing Point (Asymptotic Zero) As your animation runs, the spiraling red ball approaches its vanishing point (asymptotic zero), which as mentioned earlier, is the Flash origin located at (0, 0) or upper left position of your screen. This is a little annoying, but can be easily adjusted by moving your vanishing point. This is done by adding a vanishing point coordinate value to your ball x and y positions. To center the vanishing point to the Flash stage you must import the stage class and use stage.stageWidth/2 (which returns the x center point) and stage.stageHeight/2 (which returns the y centerpoint) as shown here: ball.x = 300*Math.sin(myAngle)*scale+stage.stageWidth/2; ball.y = 300*Math.cos(myAngle)*scale+stage.stageHeight/2;
The results of this change are shown in Figure 1.6.
12
Chapter 1: Understanding Flash3D
Vanishing at the origin (0, 0)
Adjusted Vanishing Point (stageWidth/2, stageHeight/2)
Figure 1-6 The stage class is very useful in positioning your Flash objects and will be addressed in more detail in the next chapter.
Adding a Camera Along with 3D comes the concept of a camera. Theoretically a camera is just a point in 3D space that acts as a point of view of that space. Changing the position of your camera lets you change your view in your 3D world. But in reality your camera isn’t moving, it’s just offsetting everything else in relation to itself. So for example, if you want your camera to move through a maze, you have to move the maze around your camera – not the camera around the maze. Programmatically you accomplish this by creating a camera object and giving it x, y, and z values. You use these values to adjust the position of your objects in your scene (represented by the statement below). //create camera = camera.x camera.y camera.z
your camera object new Object(); = 10; = 10; = 100;
//loop through for all elements in your scene scale = focalLength/(focalLength + this.z - camera.z); this.x = (this.x - camera.x) * scale; this.y = (this.y - camera.y) * scale; this.xscale = this.yscale = 100 * scale;
It’s important to understand that by putting the camera.z in the numerator of your scale equation you’ve actually changed the position of your singularity. This can produce some weird effects since at the singularity your object will blow up in size and then flip. You could continue to extend this train of thought adding many other features such as rotation and scaling, as was done with Papervision3D. But there is a better way!
13
Part 1: Getting Started In CS4 the perspective scaling and rotation are automatically built into Flash. It’s still important to know the concepts above when working with Flash 10 (that’s why they were included), but it’s just not necessary to do all this work. To illustrate the point, rebuild your spiraling 19 line animation engine in CS4.
Using CS4 (3D Flash 10 Engine in 13 Lines of Code) Using CS4 you only need 13 lines of code to create the same animation that took 18 lines above. Saving 5 lines may not seem like much, but it enables you to drop your perspective scaling and let the Flash 10 engine do all the work. import flash.display.Sprite;//imports sprite class var myAngle:Number =0;//Angle of ball var ball:Sprite = new Sprite();//instantiates ball sprite ball.graphics.beginFill(0xFF0000);//Assign a ball color ball.graphics.drawCircle(0, 0, 40);//draws your ball at 0,0 ball.graphics.endFill();//ends the fill addChild(ball);//adds the ball to the stage addEventListener(Event.ENTER_FRAME, myonEnterFrame);//loops equations function myonEnterFrame(event:Event):void{ myAngle=myAngle+.1;//iterates angle ball.x = 300*Math.sin(myAngle); //ball orbit x ball.y = 300*Math.cos(myAngle); //ball orbit y ball.z = 2000*Math.sin(myAngle/10);} //increments z and changes sign
Allowing Flash 10 to do the perspective scaling gives you much leverage in building 3D applications easily. Essentially, it’s what Papervision3D does, but now that power is built directly into the Flash 10 player with no complicated set up or massive imports. Not that you’re going to abandon Papervision3D . . . that’s what this book is about. But whenever a better way to do it exists in CS4, we’ll cover it as much as space allows.
Vanishing Point After running the program above, you’ll notice that the vanishing point moved. That’s because Flash 10 automatically set the vanishing point. It’s set upon initiation of the swf, based on the size set in the document properties panel. But there’s a glitch in Flex. Flex Builder ’s default swf has a size of 500375 pixels, and you’ll probably never create an swf that size. As a result, all your vanishing points will be off. The solution is to use the PerspectiveProjection class to set your vanishing point. To set your display object to center stage, for example, you would use the following command: myDisplayObject.transform.perspectiveProjections.projectionCenter = new Point(stage.stageWidth/2, stage.stageHeight/2);
Not only does it set the vanishing point for your display object (myDisplayObject in this case), but also its children. And that’s the power of CS4’s new display object. When applying transforms to a CS4 display object, its children are transformed as well. Papervision3D does the same thing with its DisplayObject3D, but now it’s inherent in the Flash10 player. The flash.display.DisplayObject class contains the z property and new rotation and scaling properties for manipulating display objects in 3D space.
14
Chapter 1: Understanding Flash3D
Using the Big 3 Fundamental to 3D graphics and physics are three fundamental motions: translation, rotation and scaling. All motions (at least for Newtonian motion) can be broken down into combinations of these three motions. You can apply these 3D transformations all at once using a Matrix3D object. You can rotate, scale, and then move an object by applying three separate transformations or more efficiently by using one Matrix3D transformation. It’s important to remember that these matrix operations aren’t generally commutative: which means that applying a rotation and then translation won’t necessarily give the same results as applying the reverse order (a translation then rotation). The following code snippet shows how to cascade a series of transformations: rotation, scale, translation, and then rotation again. var matrix:Matrix3D = myDisplayObject.transform.matrix3D; matrix.appendRotation(45, Vector3D.Y_AXIS); matrix.appendScale(2, 1, 3); matrix.appendTranslation(10, 150, -300); matrix.appendRotation(10, Vector3D.X_AXIS); myDisplayObject.transform.matrix3D = matrix;
Performing difficult matrix maths is unnecessary . . . that’s great news! Adobe has made it pretty easy to do this just by applying the simple transformations shown above. And in many cases it’s done automatically without the user even knowing it’s being performed. Consider translation, for example, when you explicitly set the z property of a display object to a numeric value, the object automatically creates a 3D transformation matrix. It all happens behind the scenes giving you more time to concentrate on building stellar 3D experiences.
Translation Adding a native z coordinate, in Flash 10, enables you to treat z just as you’ve treated x and y in the past. But using translation doesn’t just mean you’re traveling in a straight line. You can use z to constrain an element to a 3D trajectory path. As an example, consider a parametric path on a 3D surface such as a torus. The parametric curves for a torus (and any other 3D surface) can be found at WolframMathWorld (www.mathworld.wolfram.com). So what’s a parametric equation? Parametric equations are a set of equations that define the coordinates of the dependent variables (x, y and z) of a curve or surface in terms of one or more independent variables or parameters. That’s a mouthful, but basically if you iterate over the range of your parameters (of the parametric equation) your torus will be plotted in 3D space. This is a very useful device as it gives you the vertices of your torus, and in Appendix A the parametric equation for a number of 3D objects are given.
15
Part 1: Getting Started The parametric equations for a torus are: x=(c+a*cos(v))cos(u) y=(c+a*cos(v))sin(u) z=a*sin(v)
where c is the donut radius, and a is the tube radius. And u is the parameter that takes you around the larger radius (the donut) and v around the smaller tube radius as shown in Figure 1.7. Z
tube radius a
u
V C donut radius
Figure 1-7
You now use these equations to create a parametric path on your torus. What you want to do is have your graphical element spiral around the torus. You can accomplish this by iterating the parameters u and v simultaneously adjusting v to get to the desired spiral velocity. The parametric path is extremely easy to execute. Just take the code from the 3D Flash 10 Engine in 13 lines and substitute your parametric equations for ball.x, ball.y, and ball.z as shown below: import flash.display.Sprite;//imports sprite class var myAngle:Number =0;//Angle of ball var ball:Sprite = new Sprite();//instantiates ball sprite ball.graphics.beginFill(0xFF0000);//Assign a ball color ball.graphics.drawCircle(0, 0, 10);//draws your ball at 0,0 ball.graphics.endFill();//ends the fill addChild(ball);//adds the ball to the stage addEventListener(Event.ENTER_FRAME, myonEnterFrame);//loops equations function myonEnterFrame(event:Event):void{ myAngle=myAngle+.1;//iterates angle //ball parametric orbit x ball.x = (100+50*Math.cos(2*myAngle))*Math.cos(myAngle/4)+200; //ball parametric orbit y ball.y = (100+50*Math.cos(2*myAngle))*Math.sin(myAngle/4)+200; //ball parametric orbit z ball.z = 50*Math.sin(2*myAngle);}
It works flawlessly, but a single ball isn’t very exciting. Now add a few more balls to your parametric path.
16
Chapter 1: Understanding Flash3D Creating a Parametric Particle System (Torus Worm) In the previous section, you learned how to get a single element orbiting a torus using the parametric equations of a torus. Here, you multiply that single element by 100 and create a parametric particle system, which orbits (or worms around) your torus. You get a more formal treatment of particles in the “Gaming” section of this book. But for the most part, anything with more than one element can be treated as a particle system. Treating elements as particles has a number of advantages. Primarily, particles are easily handled mathematically. You’ll use particle systems extensively throughout this book. Building a particle system in Flash is relatively easy, and in this section, you cover the basics. To build the orbiting worm around a torus (parametric particle system), follow the steps below:
1.
Declare the number of particles to be created and create an array to place your particles in. This is the key to working with particles. Using the array “particles_ary” you’re able to control position, color, and alpha of each particle.
var numOfParticles:uint = 100; var particles_ary:Array = [];
2.
Use a “for” loop to run the updateStage() function, which creates your particles. Once the particles are created run the addEventListener method, which starts updating the loop for each particle on every frame event. for(var i:uint = 0; i < numOfParticles; i++) { updateStage(); numVar++; //Start Looping if(numVar==numOfParticles){ addEventListener(Event.ENTER_FRAME, myonEnterFrame);} }
3.
Draw your balls (particles) to the stage and place them in a particle array. A random color is assigned to each ball using the Math.random()*0xffffff method. //Draw a Ball var ball:Sprite = new Sprite //Assign a random ball color ball.graphics.beginFill(Math.random()*0xffffff); //draws your ball at 0,0 ball.graphics.drawCircle(0, 0, ballRadius); ball.graphics.endFill();//ends the fill //Add ball to the stage addChild(ball); //Push the ball into a particle array particles_ary.push(ball);
4.
With each onEnterFrame loop you update all your particle positions using myAngle+i/20 that separates the different particles in angle by the ratio of i/20. This causes your particles to line up on the torus and move across its surface as the myAngle variable is iterated. Use the
17
Part 1: Getting Started particles_ary[i].alpha method to change the alpha of each particle based on their ith position. Center the particle system on the stage by adding CenterX, and CenterY to the particle x, y positions. for(var i:uint = 0; i < particles_ary.length; i++) { //ball parametric orbit x var newAngle:Number=myAngle+i/20; particles_ary[i].alpha=1/(i+1)+.2; particles_ary[i].x = (100+60*Math.cos(2*newAngle))*Math.cos(newAngle/ 4)+CenterX; //ball parametric orbit y particles_ary[i].y = (100+60*Math.cos(2*newAngle))*Math.sin(newAngle/ 4)+CenterY; //ball parametric orbit z particles_ary[i].z = 60*Math.sin(2*newAngle); }
The results are shown in Figure 1.8 and result in a wormlike entity orbiting a torus. The key to creating particle systems and controlling them is to stuff the individual particles into an array upon creation. And then iterate over each particle during the frame loop of the animation sequence.
Figure 1-8
Putting it all together, the entire code is show below: //imports sprite and stage class import flash.display.Sprite; import flash.display.Stage; //Add Particle Array var numOfParticles:uint = 100; var particles_ary:Array = []; var numVar:Number=0; var ballRadius:Number=10; //Stage Center var CenterX:Number = stage.stageWidth/2; var CenterY:Number = stage.stageHeight/2;
18
Chapter 1: Understanding Flash3D var myAngle:Number =0;//Angle of ball //Add Multiple Particles function updateStage():void { //Draw a Ball var ball:Sprite = new Sprite //Assign a random ball color ball.graphics.beginFill(Math.random()*0xffffff); //draws your ball at 0,0 ball.graphics.drawCircle(0, 0, ballRadius); ball.graphics.endFill();//ends the fill //Add ball to the stage addChild(ball); //Push the ball into a particle array particles_ary.push(ball); } //Place Particles on the Stage for(var i:uint = 0; i < numOfParticles; i++) { updateStage(); numVar++; //Start Looping if(numVar==numOfParticles){ addEventListener(Event.ENTER_FRAME, myonEnterFrame);} } //Looping function function myonEnterFrame(event:Event):void{ myAngle=myAngle-.1;//iterates angle for(var i:uint = 0; i < particles_ary.length; i++) { //ball parametric orbit x var newAngle:Number=myAngle+i/20; particles_ary[i].alpha=1/(i+1)+.2; particles_ary[i].x = (100+60*Math.cos(2*newAngle))*Math.cos(newAngle/4)+CenterX; //ball parametric orbit y particles_ary[i].y = (100+60*Math.cos(2*newAngle))*Math.sin(newAngle/4)+CenterY; //ball parametric orbit z particles_ary[i].z = 60*Math.sin(2*newAngle);
}}
No discussion about particles would be complete without mention of Seb Lee Delisle, an expert in particle systems and one of the Papervision3D team members. Seb has a great site that covers particle creation in Papervision3D at http://www.sebleedelisle.com/. Next on the list of particle super stars is the
19
Part 1: Getting Started Flint-Particle-System (at http://flintparticles.org/), a robust open source project found on Google Code, which can be used to create impressive particle systems for Papervision3D. However, the particle landscape is changing. Having a native 3D and pixel bender with the release of CS4 greatly simplifies the process of building particle systems in 3D (as shown in the code above).
Depth Sorting Shuffling Objects so that the ones closer to your viewpoint appear on top of objects farther away is called depth sorting (or z-sorting). One big advantage that Papervision3D has over CS4 is automatic depth sorting. In CS4, sorting algorithms must be written and unfortunately they’re dependent on object nesting. When creating a 3D carousel for example you need to z-sort the objects as they spin to put the closest one on top of the stack. The technical name is transposition and in AS2 it was easily accomplished using the swapDepths method. But in AS3 it’s a little more complicated.
Objects on the Stage In AS3, the display list functions as an array and each display object has an index. The index starts at zero and goes up to the number of objects on your stage where index zero is the bottom object. So since the display object is a child you can change its position using the setChildIndex method. As all your objects are in an array, you can sort that array by z and then set the indices of your array objects based on z. And that’s how it’s presently done! It uses the same code structure variables as the torus worm. Here’s a sort code snippet that illustrates the concept of sorting: //Sorting function sortParticles():void { particles_ary.sortOn(“z”, Array.DESCENDING|Array.NUMERIC); for(var i:int = 0; i < particles_ary.length; i++) { addChildAt(particles_ary[i] as Sprite, i); } }
Using the pipe Array.DESCENDING|Array.NUMERIC forces the z value to be sorted in reverse numeric order. Particles are sorted from high to low based on their z value. Thus, the objects further away from your projection plane get a lower index value, placing them on the bottom of the stack. This method needs to be called each time your 3D engine iterates the position of your objects. Typically this occurs on an onEnterFrame event. Unfortunately, you’re stuck with manual sorting. But expect sorting to be native to the next version of the Flash Player.
Objects in a Display Container In many examples in this book, you won’t render multiple objects directly to the stage, but inside a display container. This approach works well when you rotate multiple objects. Instead of creating a complex trig algorithm to rotate each individual object you can just rotate the display container.
20
Chapter 1: Understanding Flash3D But in CS4 there’s a problem with this. When using the sorting algorithm (above) you sort the elements on the z-axis internal to the container. But when the container is rotated the objects rotate with it and are no longer sorted correctly. Essentially, there are two z values: z inside the container and z outside the container. To sort these correctly, you must sort with respect to the z axis outside your container. You must transform the local coordinates inside your container to the stage coordinates (or z outside your container). Keith Peters in his advanced book on AS3 Animation proposed the following solution for the container-sorting problem. //Sorting function sortParticles():void { particles_ary.sort(particleZSort); for(var i:int = 0; i < particles_ary.length; i++) { myDisplayObject.addChildAt(particles_ary[i] as Sprite, i); } } // function particleZSort(particle1:DisplayObject,particle2:DisplayObject):int { var zpos1:Vector3D = particle1.transform.matrix3D.position; zpos1 = myDisplayObject.transform.matrix3D.deltaTransformVector(zpos1); var zpos2:Vector3D = particle2.transform.matrix3D.position; zpos2 = myDisplayObject.transform.matrix3D.deltaTransformVector(zpos2); return zpos2.z - zpos1.z; }
The particles_ary.sort method passes in the particleZSort function as a parameter. The particleZSort function is called multiple times with pairs of particles during a sort, returning a negative number if the first particle should be placed in front of the second. The Matrix3D and Vector3D methods are used to keep track of your objects’ rotated positions, which we discuss in greater detail later in Chapter 3. This method is computationally intensive, but works surprisingly well (as you can see from the Carousel and Image Ball examples next).
Rotation You’ve already met basic sinusoidal functions, such as sine and cosine, in this chapter: it’s these functions that give you the ability to rotate elements in space. Even though rotation is inherent in Papervision3D (and Flash 10), you still use these functions occasionally when manipulating elements in 3D space. For example, in the gaming section of this book, you use these equations to rotate a Rubik’s cube.
21
Part 1: Getting Started 3D Rotation To rotate x, y, z points in 3D space you use the following equations: xnew = xold*cos(angleZ) - yold*sin(angleZ) ynew = xold*sin(angleZ) + yold*cos(angleZ) xnew = xold*cos(angleY) - zold*sin(angleY) znew = xold*sin(angleY) + zold*cos(angleY) ynew = yold*cos(angleX) - zold*sin(angleX) znew = yold*sin(angleX) + zold*cos(angleX) There are actually two ways to rotate elements in Papervision3D; using the preceding matrix equations or using quaternions. Most 3D engines start with the preceding matrix equations.
Flash 10 Rotation In Flash 10, rotation around the x, y, and z-axis is clockwise as angle increases. Rotation around the x, y, and z-axis are accomplished using the rotationX, rotationY, and rotationZ commands respectively. Just as in Papervision3D, the rotational values are given in degrees as opposed to radians, which had been the practice in Flash previously. But when using sine and cosine functions you still use radians (see Figure 1.9).
Y rotationY
Z rotationX
X
X
rotationZ Z Y
Figure 1-9
22
Chapter 1: Understanding Flash3D The conversion between radians and degrees and vice-versa is given below: Radians = Degrees*PI/180 Degrees = Radians*180/PI
Although these conversions are very simple, you use them all the time.
Creating a Carousel and Image Ball Two of the most popular navigation devices created using Papervision3D are the carousel and image ball. As done in the torus worm example, you’ll treat the planes in the carousel and image ball as particles. Place these particles into a display object so you can rotate them with a single rotation command. But in this case (as opposed to the torus worm example), you’ll need to sort your planes according to their z-positions. Otherwise, as you rotate your display container, your planes will not overlap correctly.
Carousel Creating a carousel is very similar to creating the torus worm demonstrated earlier. As in the torus worm case, the planes of the carousel are treated as particles. And the planes are placed in Sprite display objects with the variable name myDisplayObject. Placing the planes in a display object gives you the ability to rotate all the planes at once using the rotationY method. The individual planes are created using the drawRect method. var plane:Sprite = new Sprite();//instantiates plane sprite plane.graphics.beginFill(Math.random()*0xffffff);//Assign a plane color plane.graphics.drawRect(-60, -60, 80, 80);//draws your plane at 0,0 plane.graphics.endFill();//ends the fill
Using a little trig to place your planes is at the heart of the code. The planes are set around a circle of radius 200 pixels using sine and cosine functions. plane.x = Math.cos(angle) * 200; plane.z = Math.sin(angle) * 200;
But the orientation of each plane is not correct. Each plane is facing the same direction. To align the planes around the carousel correctly you use the rotationY method. Dividing -360 by the number of particles, multiplying by the iteration variable i, and adding 90 degrees sets the planes to the right orientation. plane.rotationY = -360 / numOfParticles * i + 90;
Once the planes are placed correctly in the carousel they’re sorted using the container-sorting algorithm discussed in the previous section (see Figure 1.10).
23
Part 1: Getting Started
Figure 1-10 Finally the display container is rotated based on mouse movement using a simple mouseX displacement algorithm. function myonEnterFrame(event:Event):void { myDisplayObject.rotationY += (CenterX - mouseX) * .01; sortParticles(); }
The complete carousel code is listed here: //imports classes import flash.display.DisplayObject; import flash.display.Sprite; import flash.display.Stage; import flash.geom.Vector3D; //Add Particle Array var numOfParticles:uint = 12; var particles_ary:Array = []; var numVar:Number=0; var myDisplayObject:Sprite= new Sprite(); var planeRadius:Number=20; //Stage Center var CenterX:Number = stage.stageWidth/2; var CenterY:Number = stage.stageHeight/2;
//Place Planes on the Stage for(var i:uint = 0; i < numOfParticles; i++) { //Draw a plane var plane:Sprite = new Sprite();//instantiates plane sprite plane.graphics.beginFill(Math.random()*0xffffff);//Assign a plane color plane.graphics.drawRect(-60, -60, 80, 80);//draws your plane at 0,0 plane.graphics.endFill();//ends the fill //Add plane to the stage myDisplayObject.addChild(plane); var angle:Number = Math.PI * 2 / numOfParticles * i; plane.x = Math.cos(angle) * 200; plane.z = Math.sin(angle) * 200; plane.y =0;
24
Chapter 1: Understanding Flash3D plane.rotationY = -360 / numOfParticles * i + 90; plane.alpha=1; particles_ary.push(plane); numVar++; //Start Looping if(numVar==numOfParticles) { addEventListener(Event.ENTER_FRAME, myonEnterFrame); addChild(myDisplayObject); myDisplayObject.rotationX=0; myDisplayObject.x=CenterX; myDisplayObject.y=CenterY; } } //Sorting function sortParticles():void { particles_ary.sort(particleZSort); for(var i:int = 0; i < particles_ary.length; i++) { myDisplayObject.addChildAt(particles_ary[i] as Sprite, i); } } function particleZSort(particle1:DisplayObject, particle2:DisplayObject):int { var zpos1:Vector3D = particle1.transform.matrix3D.position; zpos1 = myDisplayObject.transform.matrix3D.deltaTransformVector(zpos1); var zpos2:Vector3D = particle2.transform.matrix3D.position; zpos2 = myDisplayObject.transform.matrix3D.deltaTransformVector(zpos2); return zpos2.z - zpos1.z; } //Looping function function myonEnterFrame(event:Event):void { myDisplayObject.rotationY += (CenterX - mouseX) * .01; sortParticles(); }
Image Ball An image ball is just a bunch of images equally distributed around a sphere. The trick to distributing the images correctly is to use the parametric equation for a sphere, which can be found from WolframMathWorld. Iterating through the different rows and columns of the parametric equations distributes the planes to their correct positions. But they won’t be oriented correctly. plane.x = radius*Math.sin(i*pStep)*Math.sin(j*tStep); plane.z = -radius*Math.sin(i*pStep)*Math.cos(j*tStep); plane.y =-radius*Math.cos(i*pStep);
25
Part 1: Getting Started You must orient the planes to the correct angles so that they uniformly hug the sphere as shown in Figure 1.11. plane.rotationX=phiTilt[i]; plane.rotationY=-j*thetaStep[i];
For this particular example the vectors for the plane positions and angles were obtained from Flash & Math (http://www.flashandmath.com/).
Figure 1-11
As in the previous example, you use the z-sorter and mouseX displacement algorithm code to sort the rectangles correctly in depth and move the entire container using the rotationY method. The complete image ball code is listed below: //imports sprite and stage class import flash.display.DisplayObject; import flash.display.Sprite; import flash.display.Stage; import flash.geom.Vector3D; //Add Particle Array var particles_ary:Array = []; var myDisplayObject:Sprite= new Sprite(); //Image Ball Parameters //Parameters adapted from Flash & Math // http://www.flashandmath.com/ var jLen:Vector.=new Vector.(); jLen=Vector.([1,6,10,12,10,6,1]); var thetaStep:Vector.=new Vector.(); thetaStep=Vector.([0,60,36,30,36,60,0]);
26
Chapter 1: Understanding Flash3D //The vertical angle between circles. var phiStep:Number=30; var phiTilt:Vector.=new Vector.(); phiTilt=Vector.([-90,-60,-30,0,30,60,90]); //radius of the Sphere var radius:Number=180; //Stage Center var CenterX:Number = stage.stageWidth/2; var CenterY:Number = stage.stageHeight/2; //Place Particles on the Stage var i:int; var j:int; var tStep:Number; var pStep:Number=phiStep*Math.PI/180; for(i=0;i> 8 & 0xFF; yourColor >> & 0xFF;
Chapter 4: Adding Materials where >> is the bitwise right shift operator and is used to shift to the required color position. Fractional bits (or bits shifted to far right) are discarded and the “&0xFF” zeroes out the first part of the color. However, there’s a better way to go about this. In Pixel Bender, you don’t use the bit shift operator. Instead, you just refer to the color by dot syntax. That’s a lot, and it’s going to be changing, especially with Pixel Bender, as you’ll see in the next chapter. You now put all five of the shaders together into a single application. As you’ve already seen how each one of the shaders works, there’s no need to go through the code line by line. The code is well documented, but if you have any difficulties with it, have a look at the book’s website video that corresponds to this section. package { // Flash imports import flash.display.BitmapData; import flash.display.Loader; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; import flash.net.URLRequest; // Papervision3D imports import org.papervision3d.lights.PointLight3D; import org.papervision3d.core.proto.MaterialObject3D; import org.papervision3d.materials.shadematerials.CellMaterial; import org.papervision3d.materials.shadematerials.FlatShadeMaterial; import org.papervision3d.materials.shadematerials.GouraudMaterial; import org.papervision3d.materials.shadematerials.PhongMaterial; import org.papervision3d.materials.shadematerials.EnvMapMaterial; import org.papervision3d.objects.DisplayObject3D; import org.papervision3d.objects.primitives.*; import org.papervision3d.view.BasicView; public class FiveShadersSphere extends BasicView { // Declare Cylinder variables private var cyl1:Cylinder; private var cyl2:Cylinder; private var cyl3:Cylinder; private var cyl4:Cylinder; private var cyl5:Cylinder; // Declare Cylinder holder private var myHolder:DisplayObject3D; private var vpX:Number = stage.stageWidth/2; private var vpY:Number = stage.stageHeight/2; public function FiveShadersSphere() { super(0, 0, true, false); // Initialize Papervision3D initPV3D();
(continued)
157
Part 1: Getting Started (continued) // Create your objects createObjects(); } private function initPV3D():void { // Set Stage stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; // Set the background to black opaqueBackground = 0xffffff; // Place camera.x camera.y camera.z
your camera = 0; = 400; = -100;
} private function createObjects():void { // Create your point light source var light:PointLight3D = new PointLight3D(true); light.x = 100; light.y = 1000; light.z = -400; // Create your flat shade material and apply it to your cylinder var flatShadedMaterial:MaterialObject3D = new FlatShadeMaterial(light, 0x7799FF, 0x000522); cyl1 = new Cylinder(flatShadedMaterial, 50, 10, 20, 10); cyl1.x = -240; // Create your gouraud material and apply it to your cylinder var gouraudMaterial:MaterialObject3D = new GouraudMaterial(light, 0x7799FF, 0x000522); cyl2 = new Cylinder(gouraudMaterial, 50, 10, 20, 10); cyl2.x = -120; // Create your cell material and apply it to your cylinder var cellMaterial:MaterialObject3D = new CellMaterial(light, 0x7799FF, 0x000522, 5); cyl3 = new Cylinder(cellMaterial, 50, 10, 20,10); cyl3.x =0; // Create your phong material and apply it to your cylinder var phongMaterial:MaterialObject3D = new PhongMaterial(light, 0x7799FF, 0x000522, 150); cyl4 = new Cylinder(phongMaterial, 50, 10, 20, 10); cyl4.x = 120;
158
Chapter 4: Adding Materials // Add an image for your environment map var img:Loader = new Loader(); img.load(new URLRequest(“assets/envmap15.jpg”)); img.contentLoaderInfo.addEventListener(Event.COMPLETE, initenv);
// Create your environment material and apply it to your cylinder function initenv(event:Event):void{ var envMap:BitmapData = event.target.content.bitmapData; var environment:EnvMapMaterial = new EnvMapMaterial(light, envMap, envMap, 0x555555); cyl5 = new Cylinder(environment,50,10,20,10); cyl5.x = 240; myHolder.addChild(cyl5);}
// Create a holder container for your cylinders myHolder = new DisplayObject3D(); myHolder.addChild(cyl1); myHolder.addChild(cyl2); myHolder.addChild(cyl3); myHolder.addChild(cyl4);
// Add your Cylinders and light to the scene scene.addChild(myHolder); scene.addChild(light); // Start rendering the scene startRendering(); } override protected function onRenderTick(event:Event=null):void { // Rotate your cylinders cyl1.yaw(-2); cyl2.yaw(-2); cyl3.yaw(-2); cyl4.yaw(-2); cyl5.yaw(-2); cyl1.roll(-2); cyl2.roll(-2); cyl3.roll(-2); cyl4.roll(-2); cyl5.roll(-2); // Rotate Holder Container var angleX:Number = (mouseY - vpY) * .5; var angleY:Number = (mouseX - vpX) * .5; myHolder.rotationZ = -angleY; myHolder.rotationX = -angleX;
(continued)
159
Part 1: Getting Started (continued) // Call the BasicView renderer super.onRenderTick(event); } } }
As cool as Papervision3D is, it lacks an essential ingredient to having even a nominal lighting system: brightness. In the following section, you find out how to add brightness to Papervision3D.
Adding Brightness If you want a sunrise you must have brightness control. The PV3D point light source only has a position parameter, used primarily to calculate the angle of the triangle normals (as discussed earlier) – no brightness factor. However, adding brightness is easy to do; the main issue is where to put it. Brightness can be viewed as a physical parameter like mass, velocity, and acceleration. The problem is that where the quantities go is not always intuitive. Some programs create duplicate physical primitives (like the WOW physics engine) and you end up double processing your primitive to get physics to work. That’s a bad idea, resulting in a major processor drain. To be efficient, the physical parameters need to be incorporated directly into Papervision3D. The other option is to put them on each and every primitive, but then you’ve got a ton of coding to do and you end up with some that have it and some that don’t.
Updating DisplayObject3D Every object in Papervision3D is a DisplayObject3D, so the best place to put physical parameters is in the DisplayObject3D class. That way every object in Papervision3D possesses the ability to have physical parameters, avoiding double processing, and special objects with and without physics. Making this decision really is the hardest part of all. All you have to do now is open up the DisplayObject3D class (found in the org/papervision3d/ folder) and add brightness. First create a brightness property and place it with the other properties. Set brightness equal to 1. private var _brightness:Number=1;
Next place the following getter/setter method with the other getter setters. public function get brightness():Number { return this._brightness; } public function set brightness(value:Number):void { this._brightness = value; }
Because your light is a DisplayObject3D, it immediately inherits brightness.
160
Chapter 4: Adding Materials You can use this method to add any physical property that you want to your DisplayObject3D class, such as mass, velocity, or acceleration.
Updating Shaders All you have to do now is update all the shaders with your new brightness parameter and you’re done. As discussed earlier, Papervision3D has five shaders: flat, cell, gouraud, phong, and environment. Open up the shaders, which are located in the org/papervision3d/materials/shadermaterials, and make the following modifications:
Flat Open up the FlatShadeMaterial and multiply the light brightness by the zd factor. zAngle = zd*0xff*light.brightness;
Gouraud Open up the GouraudShadeMaterial and multiply the light brightness by the p0, p1, and p2 factors. p0 *= 127*light.brightness; p1 *= 127*light.brightness; p2 *= 127*light.brightness;
Cell, Phong, Environment (three-in-one) Open up the EnvMapMaterial class and make the following changes to the p0, q0, p1, q1, p2, and q2 equations. It may seem like a lot, but all you’re really doing is multiplying the dot product function by (2-light.brightness). The changes are shown in bold below. p0 = lightmapHalfwidth*(face3D.v0.normal.x * lightMatrix.n11 + face3D.v0.normal.y * lightMatrix.n12 + face3D.v0.normal.z * lightMatrix.n13)*(2-light.brightness) +lightmapHalfwidth; q0 = lightmapHalfheight*(face3D.v0.normal.x * lightMatrix.n21 + face3D.v0.normal.y * lightMatrix.n22 + face3D.v0.normal.z * lightMatrix.n23)*(2-light.brightness)+lightma pHalfheight; p1 = lightmapHalfwidth*(face3D.v1.normal.x * lightMatrix.n11 + face3D.v1.normal.y * lightMatrix.n12 + face3D.v1.normal.z * lightMatrix.n13)*(2-light.brightness)+lightm apHalfwidth; q1 = lightmapHalfheight*(face3D.v1.normal.x * lightMatrix.n21 + face3D.v1.normal.y * lightMatrix.n22 + face3D.v1.normal.z * lightMatrix.n23)*(2-light.brightness)+lightma pHalfheight; p2 = lightmapHalfwidth*(face3D.v2.normal.x * lightMatrix.n11 + face3D.v2.normal.y * lightMatrix.n12 + face3D.v2.normal.z * lightMatrix.n13)*(2-light.brightness)+lightm apHalfwidth; q2 = lightmapHalfheight*(face3D.v2.normal.x * lightMatrix.n21 + face3D.v2.normal.y * lightMatrix.n22 + face3D.v2.normal.z * lightMatrix.n23)*(2-light.brightness) +lightmapHalfheight;
That’s it. Just run your shaders like you normally do, adjusting your brightness in your animation loop and Ta da . . . instant sunrise!
161
Part 1: Getting Started Changing the brightness of a shaded object is now as easy as just setting its light brightness property, as follows: oscbright++; light.brightness=Math.abs(Math.sin(oscbright/20));
As oscbright increments the sine function, the brightness of your light source oscillates. The results are shown in Figure 4.14.
Figure 4-14 To see the entire code, download it from the ActionScript Project folder under Chapter 4, FiveShaderBright.as. Finally, keep your brightness value between 0 and 1, or your program won’t perform correctly. In the book’s downloadable source, the shader material classes that were changed have a Brightness name added to the end of them. You don’t have to do this. It’s a precautionary measure. If you mess up, you can always go back to the original class. In the next chapter, you extend this concept and add brightness to a CS4 Blender model.
Bump Map No discussion about materials would be complete without Bump Maps. They’re about the coolest thing in Papervision3D, but require much CPU energy to function – and that’s a bummer. Bump mapping is essentially an optical illusion; a technique where each pixel has a heightmap applied to it, giving the appearance of a detailed surface representation without actually creating that complex surface using vertices. In this section, you put a bump map inside a panorama. Sounds like a lot to do. But don’t freak. It’s just a combination of a bump map plus a pano. And a pano is just an image on a sphere (that you sit inside of). Start by creating a bump map.
Creating a Bump Map You can find the shader class for creating a bump map in the org/papervision3d/materials/shaders folder. Two good shaders to choose from are the PhongShader or EnvMapShader. For this demo you use EnvMap, but the procedure is similar for Phong, the exception being that you’re dealing with colors. The constructor function for the EnvMapShader class takes six input parameters:
162
Chapter 4: Adding Materials ❑
Light — a light source, primarily used for position and angle information.
❑
Envmap & backenvmap — two textures (typically the same) environment maps, front and back.
❑
AmbientColor — shadow color for when your object rotates out of the light source.
❑
BumpMap — uses a convolution filter to create a height map effect to augment your object’s pixels, giving your object an enhanced “indented or bumpy” surface look.
❑
SpecularMap — a specular map.
You’ve already encountered a number of these parameters. The object’s signature is as follows: EnvMapShader(light, envmap, backenvmap, ambientColor, bumpMap, specularMap)
The code snippet for creating a bump map is: //Create Assets for EnvMap and Bump Map var bumpmap:BitmapAsset = new graphic() as BitmapAsset; var envmap:BitmapAsset = new envmap() as BitmapAsset; var bumpmapc:BitmapData = bumpmap.bitmapData.clone(); //Add Blur to bump map clone bumpmapc.applyFilter(bumpmapc, bumpmapc.rect, new Point(), new BlurFilter(3,3,3)); //Create shade material for the bump Sphere var envShaded:EnvMapShader = new EnvMapShader(pointLight, envmap.bitmapData, envmap.bitmapData,0,bumpmapc); var shadedMaterial:ShadedMaterial = new ShadedMaterial(new BitmapMaterial(bumpmap. bitmapData),envShaded); //Instantiate your sphere and place your shaded Material on it. bumpSphere = new Sphere(shadedMaterial, 400, 16,16); scene.addChild(bumpSphere);
The bump map is essentially created from two materials; a surface material (used twice, top and bottom) and a bump map. The bump map is enhanced by using a blur filter, which though computationally expensive, really adds to the visual experience. (Try commenting out the blur filter and you’ll definitely see the difference). The environment map and bump map are combined using the ShadeMaterial class. And finally, this new ShadeMaterial is placed on your sphere – wow that’s a bunch to do to get a bump map. It’s great for small experiments, but not practical for large projects as it’s so computationally intensive. You’re now going to do something a little more interesting. You place a bump map inside of a panorama.
Putting Your Bump Map in a Panorama Starting with good images makes all the difference when working with bump maps and panoramas. Creating great looking spherical panorama images is beyond the scope of this book (see Figure 4.15), but is treated extensively on the book’s website and blog.
163
Part 1: Getting Started
Figure 4-15
Putting a bump map inside of a panorama is an easy four-step process:
1. 2.
Create your bump map using the code shown above. Create a Panorama using the following code: //Create a Movie Material to place on your pano Sphere sphereMat = new MovieMaterial(movieAsset, false); sphereMat.opposite = true; sphereMat.animated = true; //Smooth - heavy on the processor - but adds to the luster sphereMat.smooth = true; //Create the panosphere. panoSphere = new Sphere(sphereMat, 25000, 30,30); scene.addChild(panoSphere);
Using this code you essentially create a sphere at coordinates (0,0) and place a movie (swf) on it. A Flash movie is used in this case because it gives you the ability to include animation in your pano and control the animation with ActionScript. Otherwise, you could’ve used a bitmap image.
3.
Position your bumpSphere and camera and create a rotation function which lets you rotate your camera and bumpSphere symmetrically. This takes a few tries with different parameter adjustments. Your pano actually remains stationary as your bumpSphere and camera rotate. The rotation code for the bumpShere is shown below and involves a combination of sines and cosines.
private function rotateXY(ball:Sphere, angleX:Number, angleY:Number):void { var cosX:Number = Math.cos(angleX*Math.PI/180); var sinX:Number = Math.sin(angleX*Math.PI/180); var cosY:Number = Math.cos(angleY*Math.PI/180); var sinY:Number = Math.sin(angleY*Math.PI/180); var myX:Number = 1000 * sinY*cosX; var myZ:Number = 1000 * cosY*cosX-1000 ;
164
Chapter 4: Adding Materials var myY:Number = 1000 * sinX; ball.x = myX; ball.z = myZ; ball.y = -myY; }}}
4.
Use mouse listeners to throttle your application as you rotate around your environment.
Putting it all together you get the eye candy results shown in Figure 4.16. It’s more than just eye candy though. It’s an adventure that stimulates the optical cortex in a way that’s somewhat transcendental.
Figure 4-16
The complete code is listed here: package { //Import statements import flash.display.BitmapData; import flash.events.Event; import flash.events.MouseEvent; import flash.filters.BlurFilter; import flash.geom.Point; import mx.core.BitmapAsset; import mx.core.MovieClipAsset; import import import import import
org.papervision3d.cameras.CameraType; org.papervision3d.lights.PointLight3D; org.papervision3d.materials.*; org.papervision3d.materials.shaders.EnvMapShader; org.papervision3d.materials.shaders.ShadedMaterial;
(continued)
165
Part 1: Getting Started (continued) import org.papervision3d.materials.utils.*; import org.papervision3d.objects.primitives.Sphere; import org.papervision3d.view.BasicView; //Bump Pano Class public class PanoBumpDemo extends BasicView { //Embed image resources [Embed(source=”assets/library2.swf”)] private var panoAsset:Class; [Embed(source=”image/LibraryPano.jpg”)] public static var graphic:Class; [Embed(source=”image/env2.png”)] public static var envmap:Class; //Declare point light and Bump Sphere public var pointLight:PointLight3D = new PointLight3D(); public var bumpSphere:Sphere; //Declare pano Sphere private var panoSphere:Sphere; private var sphereMat:MovieMaterial; //Set mouse condition private var myMouseDown:Boolean=false; //Pano Bump Class public function PanoBumpDemo() { super(1, 1, true, false, CameraType.FREE); init3D(); startRendering(); opaqueBackground = 0; } //Initiate scene protected function init3D():void { //Create Assets for EnvMap and Bump Map var bumpmap:BitmapAsset = new graphic() as BitmapAsset; var envmap:BitmapAsset = new envmap() as BitmapAsset; var bumpmapc:BitmapData = bumpmap.bitmapData.clone(); //Add Blur to bump map clone bumpmapc.applyFilter(bumpmapc, bumpmapc.rect, new Point(), new BlurFilter(3,3,3)); //Create shade material for the bump Sphere var envShaded:EnvMapShader = new EnvMapShader(pointLight, envmap .bitmapData, envmap.bitmapData,0,bumpmapc); var shadedMaterial:ShadedMaterial = new ShadedMaterial(new BitmapMaterial(bumpmap.bitmapData),envShaded); bumpSphere = new Sphere(shadedMaterial, 400, 16,16); scene.addChild(bumpSphere);
166
Chapter 4: Adding Materials //Create the pano material var movieAsset:MovieClipAsset = new panoAsset(); sphereMat = new MovieMaterial(movieAsset, false); sphereMat.opposite = true; sphereMat.animated = true;
//Smooth - heavy on the processor - but adds to the luster sphereMat.smooth = true; //Create the panosphere. panoSphere = new Sphere(sphereMat, 25000, 30,30); scene.addChild(panoSphere); //Set up your camera camera.x = camera.y = 0; camera.z =-1000; camera.focus = 300; camera.zoom = 2; //Add your mouse listeners addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); addEventListener(MouseEvent.MOUSE_UP, onMouseUp); addEventListener(MouseEvent.ROLL_OVER, onMouseUp); //Mouse controls function onMouseDown() : void { myMouseDown = true; }; function onMouseUp() : void { myMouseDown = false; }; } //Animation Loop override protected function onRenderTick(event:Event=null):void { if (myMouseDown) { camera.rotationY += (mouseX - stage.width / 2) / 50; camera.rotationX += (mouseY - stage.height / 2) / 50; rotateXY(bumpSphere, camera.rotationX, camera.rotationY); } super.onRenderTick(event); }
(continued)
167
Part 1: Getting Started (continued) //Rotation equation for the Bump Mapped Sphere private function rotateXY(ball:Sphere, angleX:Number, angleY:Number):void { var cosX:Number = Math.cos(angleX*Math.PI/180); var sinX:Number = Math.sin(angleX*Math.PI/180); var cosY:Number = Math.cos(angleY*Math.PI/180); var sinY:Number = Math.sin(angleY*Math.PI/180); var myX:Number = 1000 * sinY*cosX; var myZ:Number = 1000 * cosY*cosX-1000 ; var myY:Number = 1000 * sinX; ball.x = myX; ball.z = myZ; ball.y = -myY; }}}
Using Fog and Composite Materials Both Fog and composite materials (like shaders) are processor intensive items and therefore we don’t address them in great detail here. However, we discuss them further in the next chapter, in which you find out about Pixel Bender. Fog requires multiple layers of colors. The way the Fog Filter works (in Papervision3D) is by generating simple colored fill layers during the render process. Unfortunately this bogs down your CPU as it tries to treat enough layers to make your foggy scene look realistic. These layers are added at slightly increasing alpha transparencies over the range of depth of your current view. To make things worse, most practical applications are difficult to execute in Papervision3D using its built-in FogFilter. The use of composite materials results in a similar situation as the FogFilter. Composite materials are combinations of materials, such as a Wireframe and a ColorMaterial: //Instantiate your ColorMaterial and WireframeMaterial wireMaterial = new WireframeMaterial(0x676767); colorMaterial = new ColorMaterial(0x434343); //Instantiate your CompositeMaterial as myCompMaterial myCompMaterial = new CompositeMaterial(); //Add your Composite Material using the addMaterial method myComposite.addMaterial(colorMaterial); //Add the Wireframe Material using the addMaterial method myComposite.addMaterial(wireMaterial); //Add your Composite Material to your 3D object mySphere = new Sphere(composite, 200, 8, 8);
168
Chapter 4: Adding Materials From the code above, you can see that composite materials work by adding one material to another in a composite material. As a result, you’re double processing both materials as you instantiate your primitive. Double processing is something you generally want to avoid whenever possible.
Summar y Congratulations, you’ve turned the corner from 3D-techno babble to building “real world” applications. Although some of the material presented in this chapter may seem dry, it’s essential to know how to improve your application’s performance. Digging into the next level of Papervision3D and adding just a few lines of code can really set your application apart from the rest. In the next chapter, you crank this up a notch as you bring in 3D models from 3DSMax, and Blender.
169
Par t II: Working with Models and Data Chapter 5: Creating 3D Models Chapter 6: Working with Particle Systems Chapter 7: Geocoding, XML, and Databases Chapter 8: Gliding on AIR
Part II: Working with Models and Data In Part II, you learn how to create your own 3D models and import them into PV3D. You learn how to create particle systems, bring Google Maps into PV3D and CS4, and build data-driven applications in Flash, Flex, and Air.
172
Creating 3D Models The most incredible news about Flash is its ability to import 3D models from programs like Blender and 3DSMax. With the advent of programs like Papervision3D, a world of ubiquitous 3D web content has appeared on the horizon. But with any new world, there are new challenges: ActionScript driven animation, 3D modeling, texture preparation, and more. Flash3D is a very broad area and your journey begins here with modeling. In this chapter, you cover the basics of modeling and how to import those models into Papervision3D. You examine commonly used modeling packages such as Blender, 3DSMax, and SketchUp. You’ll develop a technique for creating Quake 2 models, and create simple parsers for Blender and 3DSMax. Finally, you examine the powerful Collada format, and learn how to enhance your models by incorporating Pixel Bender.
Modeling for Paper vision3D Remember you’re modeling for the web. You don’t have the luxury of a large supportive game engine; everything bounces off the Flash player. Keeping your polygon (triangle) count low is essential to great performance. This is achieved in part by low polygon modeling. You might be asking at this point: how low? You’ll be rendering entire scenes with less than 2,000 polygons. Now, before that statement sends you packing, you’re going to learn some tricks to enhance this number significantly; especially when you get to portal engines and Pixel Bender. If you’re coming from a Flash background, you’re probably cheering right now: bringing in 3D models is a leap for Flash – even low polygon ones. It’s interesting how different things look depending on where you sit though, because this is a catastrophe from the standpoint of those who come from a 3D game development background.
Part II: Working with Models and Data But if you’re a cell phone developer, then you’re probably thinking: “bring it on”. That’s because you’re looking for a fluid multi-media interaction with your user. In other words, no stops, no downloads, no slow downs, and high visuals. Furthermore, as technology rapidly changes, applications continue to migrate to the web. So even if you’re used to creating 120,000-polygon models, the techniques you learn here will help you become more efficient.
Bringing Down the Count Most modeling software packages have some way to keep track of the number of polygons in your model. 3DSMax, for example, by pressing the 7 key, lets you keep track of polygon and vertex number as you model. Keeping this on as you model is a great help; you’ll be surprised how applying certain modifiers can bloat your triangle count (sometimes by an order of magnitude). By using this simple tool you get a feel for what keeps triangle count down. In addition, here are some tips that can help you create low polygon models:
174
❑
Reduce face count by simplifying geometry such as windows and doors. For example, it’s more important to show position and proportion than the trim. Use images of windows and doors as opposed to trying to model them exactly. Whenever possible, use entire building photos to model building sides.
❑
Use simplified structures. Rather than trying to create a grainy surface (or wood lattices) based on vertex manipulation (or multi-primitive placement), just use a texture that has a grainy look (or PNG lattice). This can go a long way, so use it whenever you can. Use PNG (for transparency) or JPG images at 72ppi.
❑
Keep curved surfaces to a minimal number of faces. Reduce the number of sides of a polygon. Many times you can get away with using a 3-sided polygon to draw a column as opposed to a 12-sided polygon. The savings are huge.
❑
Avoid hidden structures. If you can’t see it, don’t draw it. Many models have internal structures that are revealed when opening a door or hatch (like the inside of a tank). Separate those structures using a portal engine and don’t draw them until you need them. For example, why draw the bottom of a house or its internal walls if you can’t see it. Delete them.
❑
Be aware of levels of detail. Focus on where your user will be looking. If your user ’s eye isn’t drawn to a certain area of the screen, approximate detail with an image. Or if an object is always going to be viewed from far away, don’t detail it.
❑
Use single faces when possible. Many times a single face (or combination of single faces) can be used to approximate structures. A tree, for example, can be created from two or three single faces (as was shown in Chapter 3), as opposed to drawing multiple polygons to represent the branches and leaves.
❑
Turn repeated geometry into instantiated components, in which case your computer only has to remember a single definition. And optimize the component definition itself (such as removing backsides that aren’t seen).
❑
Paint small or insignificant structures with colors as opposed to using a texture.
❑
Use Picasa, Gimp, or Photoshop to optimize your images. Bringing down image size and file size will increase your model’s performance on the web. There’s a tradeoff between image quality and web performance. Also, use mip-mapping (discussed in the next chapter) whenever possible.
Chapter 5: Creating 3D Models ❑
Avoid lines. Drawing lines in Papervision3D is expensive; make sure that you have the lines turned off. And avoid using composite components and transparency in PV3D; it taxes your processor.
❑
When shading, use Pixel Bender or the Fast Shading techniques developed in this book.
Also, CS4 lets you use large polygon faces without getting PV3D image sag. This greatly reduces the number of polygons needed to model buildings.
Gauging Performance in PV3D In addition to watching your polygon count, Papervision3D has a built-in mechanism for judging its ability to process a scene. It’s called StatsView, and includes the following performance indicators: ❑
FPS — Frames Per Second
❑
Tri-Triangles, Sha-Shaded Triangles
❑
Lin-Lines
❑
Par-Particles
❑
Cob-Culled Objects
❑
CTr-Culled Triangles
❑
CPa-Culled Particles
❑
Fob-Filtered Objects
❑
Mem-Memory Used
Depending on what you’re working on, any number of these parameters can be useful. At a minimum you want to track frames-per-second and memory usage. Using the BasicView class to implement StatsView is a simple two-step process:
1. 2.
Import the StatsView class, import org.papervision3d.view.stats.StatsView; Declare and add StatsView to your scene: addChild(new StatsView(renderer));
The result of implementing the code is a state window in the upper left corner of your screen (see Figure 5.1), which provides all the performance parameters listed above.
Figure 5-1 But how does Papervision3D come up with these parameters?
175
Part II: Working with Models and Data How it Works You can actually implement this to optimize your scene programmatically by making changes to your scene and models based upon the output of these parameters. To accomplish this task you need to understand how they’re created.
Fps StatsView is found in the org/papervision3d/view/stats folder and extends the AbstractStatsView class. In this super class, there’s a very simple method called onFrame, which
enables you to calculate the actual frames per second. It’s shown here: protected function onFrame(event:Event):void { currentFrameTime = getTimer(); fps = 1000/(currentFrameTime-lastFrameTime); lastFrameTime = currentFrameTime; }
The onFrame method is based on the getTimer() method, which returns the number of milliseconds that have elapsed since Flash Player was initialized. Subtracting the getTimer value between the ENTER_FRAME events allows you to calculate fps. Fps is a measure of how well you’re mathematically processing your vertices. Later in the book you find out how to use this number to throttle your scene.
Memory Usage Memory usage is not a Papervision3D entity. It’s a Flash player System method and is implemented (in MB to two decimal places) using this code: System.totalMemory/1024/1024).toFixed(2)
Memory usage is a big item, it’s what makes your machine run hot, and as you add and remove items from your scene you want to keep your eye on this number.
All the Others In the RenderStatistics class (found in the org/papervision3d/core/render/data folder) StatsView picks from a host of properties to create its output list. You can easily create your own StatsView by modifying this list. There’s nothing magical about StatsView and understanding how it works allows you to use it to optimize your scene programmatically. Now that you understand a little about optimizing your models, it’s time to learn how to build them.
Choosing a Modeling Application Before you get ready to spend a million bucks on the newest and coolest 3D modeling application, you should keep in mind that low polygon modeling really levels the playing field when it comes to which application to use. As mentioned earlier in this book, “a vertex is a vertex,” which means that it doesn’t matter which application you use, as long as you can use it to create a low polygon model and export that model to PV3D.
176
Chapter 5: Creating 3D Models Expanding on this idea, the purpose of modeling (especially in Papervision3D) is to position vertices in space, create a triangular face array, unwrap it, and place a texture on your object. Most modeling packages do that . . . so the next consideration, which is the most important, is being able to export your model into a form that PV3D can use.
Blender There’s some negative chatter out there in cyberspace about Blender. In reality, Blender is a great 3D modeling tool, and it’s free! Once you get over the hump of learning Blender, you’ll find that it’s fast, crashes less than other 3D modeling apps, runs on Windows or Mac, and has a large supportive user community. You can download Blender from http://www.blender.org. Here are some important points to learning Blender: ❑
Saving and Opening Models — when saving or opening a Blender model, a Linux-like interface comes up that gives you access to your computer ’s tree structure. It’s easy to use, but looks really strange at first.
❑
Using the Space Bar — by pressing the space bar (when the main stage is selected) a menu comes up that lets you add primitives for box modeling, and edit or transform your models (see Figure 5.2).
Figure 5-2 ❑
Short Cuts — like many modeling apps, tons of keyboard short cuts are available that accelerate your ability to create models. Some of the essential ones are: e for extrude, s for shrink, r for rotate, b for select, and more . . . check out the book’s website for a full listing.
Also on the book’s blog and website are extensive video tutorials on using Blender. They’re designed to get you over the hump of learning Blender and into producing PV3D content. Once you’ve mastered the basics, you’ll find it easy to produce textured objects for PV3D or CS4 using Blender.
177
Part II: Working with Models and Data
3DSMax Besides being the industry standard, 3DSMax is one of the easiest 3D modeling packages to learn and use. If you’re only going to learn one modeling package (and can afford it) 3DSMax is the one. It’s entirely menu driven, has a host of modifiers, and a robust material editor, which works on a tree modification structure. There’s one problem though; it only works on Windows. If you only have a Mac, your next best option is Maya, which runs on both Mac and Windows and is similar to 3DSMax. In addition, 3DSMax has a tree-like materials editor, which gives you the ability to cascade multiple effect (or filters) and create a high degree of realism. Like Blender, Max has a host of shortcut keys that can be used to accelerate model creation. The book’s blog and website contain extensive video tutorials on learning 3DSMax. They’re designed to get you into the core of architectural modeling. You’ll find box modeling a dream in 3DSMax with many approaches designed to help you build that perfect model for PV3D. However, it’s important to do multiple saves of your work with 3DSMax as it has a tendency to crash. Also, make sure that you save different version names, since a crash may render your present saved version unusable.
Google SketchUp SketchUp modeling is a little different than 3DSMax and Blender. As opposed to modeling with vertices, edges, or faces, SketchUp just uses edges and faces. SketchUp is both fun and easy to learn with numerous tutorials available on the web to assist you in mastering it. And the coolest thing is that there are tons of free models and textures available on Google Warehouse found at http://sketchup .google.com/3dwarehouse/.
SketchUp is a large “free” unharnessed resource that you can use in both PV3D and CS4. When it’s high quality, drawn by someone else, and free – that’s the best of all worlds. Typical of what you may find in Google Warehouse is the model of Westminster Abbey as shown in Figure 5.3.
Figure 5-3
178
Chapter 5: Creating 3D Models SketchUp handles its textures differently than the uvmaps typically used in Blender and 3DSMax. It assigns individual bitmaps to objects. This approach is similar to how Second Life assigns its textures and sidesteps the need for complex uv modeling.
Swift3D Before PV3D, Swift3D was the only way of producing hiqh quality 3D Flash content for the web. It was used to create 3D swfs for use in Flash, but has upgraded to allow export to PV3D. You can import a 3DSMax model into Swift3D and export it as acceptable PV3D Collada files. If you’re having problems getting your 3DSMax exporters to work, going through Swift3D is a definite option. Swift3D also has a modeler based on triangle manipulation. The major upgrade needed to make Swift3D a major league hitter in Flash3D is a material editor that lets you unwrap models, create exportable uv maps, and Collada animation. Once you’ve created your models you need to bring them into Papervision3D using a parser. A parser extracts from a file the parameters required to render your model. Papervision3D has a bunch of them.
Paper vision3D Impor ters PV3D has a host of parsers designed to import model data from a handful of modeling tools: 3DSMax, Maya, SketchUp, and Quake (MD2 format). They’re found in the org/papervision3d/objects/ parsers folder. At first, this list may seem a little overwhelming. You can figure out which one to use simply by understanding what each one is (see Figure 5.4).
Figure 5-4 Here’s a short description of the various parsers: ❑
Ase — lets you load and parse ASE format objects exported from 3DS Max. This format has no animation channel.
❑
Collada — lets you parse Collada scenes. This format has no animation channel.
❑
DAE — most advanced Collada parser; designed to parse COLLADA 1.4.1 files (Maya and Max settings in the documentation). This format includes an animation channel.
179
Part II: Working with Models and Data ❑
KMZ — lets you load and parse KMZ files; has no animation channel.
❑
Max3DS — a Max parser based on Away3D’s class; has no animation channel.
❑
MD2 — loads Quake 2 (MD2 file) with an animation channel.
❑
SketchUp — helper file that assists in bitmap loading for SketchupCollada.
❑
SketchupCollada — Collada class that lets you load and parse Collada scenes with no
animation channel The most commonly used parsers are: Collada, DAE, ASE, and MD2. Both the DAE and MD2 parsers have animation channels (ability to bring in animation data and use it in PV3D), where the rest do not. In the following sections, you find out about the MD2 and Collada parsers.
What’s MD2? The MD2 format was created for the Quake II engine and has since gained popularity due to its simple, easy-to-use format. But finding a model that works with Papervision3D’s MD2 importer can sometimes be a challenge. The overall MD2 flow is shown in Figure 5.5.
Header
Frames
Triangles
Skin Names
UV Data
Vertices
Figure 5-5 In the diagram above you should recognize the familiar vertices, triangles, and UV data that’s required for all models to render. And as you’re putting textures on your models, you must also call your skin names. The procedure for putting MD2 models into PV3D is as follows:
1. 2. 3. 4. 5.
Import the MD2 parser class. Instantiate your MD2 texture as a BitmapFileMaterial. Instantiate the MD2 object. Load your MD2 model and its texture. Add your MD2 object to your scene using addChild.
The documented code snippet that enables you to do that’s provided here:
180
Chapter 5: Creating 3D Models private function initMD2():void { // Instantiate your skin as a BitmapFileMaterial var myMD2Skin:BitmapFileMaterial = new BitmapFileMaterial(“assets/mySkin.jpg”, true); // Instantiate an MD2 object (use true for animation, false otherwise) var myMD2Object:MD2 = new MD2(true); // Give your object a name so you can control it in the display list myMD2Object.name = “md2Object1”; //Load your MD2 model and its skin myMD2Object.load(‘assets/myModel.md2’, mySkin, framerate, scale); // Add your MD2 object to your scene using addChild scene.addChild(myMD2Object); }
The preceding code snippet really highlights the beauty of PV3D. It uses the same procedure that you’ve seen many times before: after importing your MD2 class, you declare a skin, an object, and add it to your scene using the addChild method. Using MD2 in PV3D was seen, at first, as the best of all worlds. MD2 models are low polygon and work well in PV3D. However, finding models that work in PV3D, or creating your own, is a specialized process. It could even be considered a lost or dying art. You can still find many great websites out there though, which use MD2. A number of model packs can be found at http://www.md2.sitters-electronics.nl/index.html and http://telias.free.fr/models_md2_menu.html. But keep in mind not all of them work and in the long run you need to be able to make your own models and modify them to be successful.
Model Pack To help you get started, included in the book’s chapter code is a small MD2 model pack. These models are released freely under the GNU license (see Figure 5.6).
Figure 5-6
181
Part II: Working with Models and Data The code required to import these models into PV3D is provided here. The code is fully documented and closely follows the code snippet above. When you get to the chapter on gaming, you’ll do more with MD2 models. But for now they just spin in a circle. package
{
import import import import
flash.events.Event; org.papervision3d.materials.BitmapFileMaterial; org.papervision3d.objects.parsers.MD2; org.papervision3d.view.BasicView;
public class ModelPack extends BasicView { // Create your MD2 types (car, airplane, helicopter, and van). private var myCar:MD2 = new MD2(); private var myAir:MD2 = new MD2(); private var myCop:MD2 = new MD2(); private var myVan:MD2 = new MD2(); private var osc:Number=0; public function ModelPack() { super(400, 400, true); opaqueBackground=”0xffffff”; initPV3D(); buildModels(); } // Initiate PV3D, with BasicView this is easy private function initPV3D():void { // Add ENTER_FRAME listener addEventListener(Event.ENTER_FRAME, render); } // Build your models private function buildModels():void { // Load skin/texture of your model var myCarSkin:BitmapFileMaterial = .png’, true); var myAirSkin:BitmapFileMaterial = airplane/air.jpg’, true); var myCopSkin:BitmapFileMaterial = helicopter1.png’, true); var myVanSkin:BitmapFileMaterial = .png’, true); var myBrdSkin:BitmapFileMaterial = bird_final.jpg’, true);
from an external Bitmap new BitmapFileMaterial(‘modelpack3/car/car new BitmapFileMaterial(‘modelpack3/ new BitmapFileMaterial(‘modelpack3/heli/ new BitmapFileMaterial(‘modelpack3/van/van new BitmapFileMaterial(‘modelpack3/bird/
// Setting model name, with this you can use methods as, getChildByName in scene object.
182
Chapter 5: Creating 3D Models myCar.name myAir.name myCop.name myVan.name
= = = =
‘myCar’; ‘myAir’; ‘myCop’; ‘myVan’;
// The load method includes (url string, material, framerate, scale) myCar.load(‘modelpack3/car/car.md2’, myCarSkin, 10, 1); myAir.load(‘modelpack3/airplane/body.md2’, myAirSkin, 10, 2); myCop.load(‘modelpack3/heli/helicopter.md2’, myCopSkin, 10, 2); myVan.load(‘modelpack3/van/van.md2’, myVanSkin, 10, .8); // Setup x, y, and z initial rotation and position//Position Car myCar.rotationX = -90; myCar.x=-540; //Position AirPlane myAir.rotationX = -90; myAir.x=-200; //Position Van myVan.rotationX = -90; myVan.x=200; //Position Helicopter myCop.rotationX = -90; myCop.x=600; // Add models to your scene scene.addChild(myCar); scene.addChild(myAir); scene.addChild(myCop); scene.addChild(myVan); // Start rendering. startRendering(); } private function render(event:Event):void { // Add rotation to the models myCar.roll(1); myAir.roll(1); myVan.roll(1); myCop.roll(1); super.onRenderTick(event); }}}
In addition to the preceding code, on the book’s website and blog is a MD2 model viewer with downloader (see Figure 5.7). From this application you can view various MD2 models and download them. It’s worth mentioning some of the highlights of that program here since it solves the basic problem of handling multiple models in PV3D.
183
Part II: Working with Models and Data
Figure 5-7
The model downloader that’s shown has a populated data grid on the right, which allows you to click on, view, and download MD2 models. The trick to handling multiple models is to treat them as particles.
The Big Trick to Handling Multiple Models In the application shown above, you’re switching from model to model seamlessly. The big trick to handling multiple models in PV3D is to treat your models as a particle system. Each model has its own display object, which is loaded into an array and is annihilated or created when needed as shown here: for(var j:uint = 0; j < markersCount; j++) { var item:MD2 = new MD2(); particles_ary.push(item); }
In the code snippet above all you do is create a unique MD2 instance for each model by iterating over them using a for loop. If you don’t do this your models become distorted as you try to populate the same container with each one of them. The MD2 containers are then pushed into an array called particles_ary, which is similar to what you would do with a particle system. As you move forward in the book you learn more about optimization. Here you discover how to remove the object from the stage, but its listener is still attached. To truly make it available for Flash trash pick up, you need to remove its event listener as well . . . but more on that in Chapter 6. movieParent.removeChild(particles_ary[prevIndex]); particles_ary[prevIndex].load(myDataGrid.selectedItem.address, mySkin, 15, 1); movieParent.addChild(particles_ary[prevIndex])
The models are indexed by the data grid and can easily be removed and added to the stage using the removeChild and addChild methods. We cover how to treat applications as a particle system in great detail in Chapter 6. To see the entire code for the model viewer, check out the book’s chapter code section and website.
184
Chapter 5: Creating 3D Models Using models from other sources can be problematic, especially if you can’t change them, aren’t sure what’s in the byte code, and can’t get copyright permission to use them. So next you find out how to make your own models in Blender and 3DSMax. And with a little practice, you’ll learn how to create great models quickly.
Creating MD2 Files (MilkShape to the Rescue) When it comes to working with or creating MD2 models, MilkShape really comes to your rescue. Initially designed for Half-Life, MilkShape is a low-polygon modeler, which now supports over 70 different file formats. Creating MD2 models is a 4 step process as shown in Figure 5.8.
Create Model
Texture and Export
Prepare in MilkShape
Export to Model Viewer
Figure 5-8
The steps are outlined as follows:
1. 2. 3. 4.
Create Your Model Texture and Export Your Model Prepare Your Model in MilkShape Export to a Model Viewer
At the center of the process of creating a MD2 model is the use of MilkShape to create the MD2 (Quake 2) format from a 3DSMax (or Maya) import. MilkShape (and a MD2 Viewer) can be downloaded from http://chumbalum.swissquake.ch/
MilkShape has many of the standard features of other 3D modelers: ❑
Select, move, rotate, scale
❑
Extrude, turn edge, subdivide
❑
Low-level editing with the vertex and face tool
❑
Primitives like sphere, box, cylinder, plane
❑
Numerous plug-ins
185
Part II: Working with Models and Data The true power of MilkShape is in its ability to import and export such a wide variety of file formats (especially Quake formats). In the following discussion, you use it to convert a 3DSMax file into the MD2 format, but the same procedure works for Maya as well. The steps for creating a MD2 model for PV3D starting with 3DSMax (or Maya) are as follows:
1. 2. 3. 4. 5. 6. 7. 8. 9.
Create your model in 3DSMax (or Maya). Export it as a 3DS file (fdx file). Import this file into MilkShape by choosing Autodesk 3DS . . . (or Maya). Click the model tab, then joint button and insert a joint by clicking on a vertex. Select all vertices (control a). Go to the joints tab and click assign. Check to make sure the animation is correct (single frame for no animation). Export as an MD2 file (ignore the no md3.qc warning not loading into Quake). Check it out using the MD2 model viewer found on the MilkShape site or the PV3D viewer provided on the book’s website.
An extensive video tutorial of the process can be found on the book’s blog and website. Before you leave MD2 you must investigate how MD2 objects are animated in PV3D. Currently, animation in PV3D can be accomplished in two ways: by using scripting to animate, or parsed external data xml (or binary file). What follows is an introduction to how PV3D takes information from an external file and creates animation. You’ll be amazed at how easy it is!
Making Things Move Animation in MD2 essentially involves drawing frames one after another. To smooth out animation, interpolation between key frames is used. In PV3D, this is accomplished by interpolating between frames with respect to time. Doing this lets you automatically generate new frames (between the key frames) that represent your model’s position at any given time. Now here’s the trick: as with bone animation, you’re not really moving your model, but rather you’re moving its vertices. It’s all based upon the following equation: Vnext=V0+ frameAlpha* (V1-V0)
where Vnext is the position of your next vertex in your keyframe-to-keyframe animation sequence. V0 is your first keyframe vertex. V1 is your second keyframe vertex, and frameAlpha is a value between 0 and 1 indicating position between startTime and endTime. The simple equation above is implemented in PV3D’s MorphChannel3D class found in the animation channel folder. Its code snippet is shown below: for(var i:int = 0; i var u:Vertex3D var v:Vertex3D var w:Vertex3D
186
< = = =
target.geometry.vertices.length; i++){ target.geometry.vertices[i]; curOutput[i]; nxtOutput ? nxtOutput[i]: curOutput[i];
Chapter 5: Creating 3D Models //Implement Vnext = V0+ frameAlpha*(V1-V0) u.x = v.x + frameAlpha * (w.x-v.x); u.y = v.y + frameAlpha * (w.y-v.y); u.z = v.z + frameAlpha * (w.z-v.z); } }
The u vector is your present frame (in between keyframe vertex positions), the v vector is your starting keyframe vertex and the w frame is your ending key frame vertex. The frameAlpha parameter is calculated in the AbstractChannel3D class (which is the super class of the MorphChannel3D class) using the following code snippet: frameAlpha = (currentTime-currentKeyFrame.time) / frameDuration;
The equation goes from 0 to 1 as the currentTime – currentKeyFrame.time approaches frameDuration. If you’re a flashmatician you’re very familiar with keyframe animation. Luckily the animation channel code used for MD2 and Collada formats are the same in PV3D. In other words, what you’ve learned here about MD2 animation applies to Collada as well.
What’s Up with KMZ? KMZ is the Google Earth format and can be exported directly from the free version of Google SketchUp. It would make sense to provide an extensive discussion of this format right about now. However, there’s a major problem with KMZ in PV3D. The KMZ parser was submitted by a third party, and isn’t supported by the PV3D team. In other words, there’s no support for it and at the time of this writing, the parser has been broken for quite a while. The PV3D team advises developers that they use it at their own risk and states that the library is incomplete. No need to worry though, you haven’t lost your ability to use SketchUp yet. SketchUp Pro exports Collada (dae) files, and when downloading a model from Google Warehouse, a Collada file is part of the standard download package. With this in mind, you now start your journey into the wonderful world of human-readable formats created in XML.
Hello Collada Started by Sony for their PlayStation 3, COLLADA stands for COLLAborative Design Activity, and was introduced with the intent of creating a standardized, portable 3D format. It has since grown into a consortium of companies and is managed by the non-profit technology consortium, the Khronos Group. Collada’s original goal of creating a useful tool for the widest possible audience continues to evolve. All of the major modeling applications such as Blender, 3DSMax, Maya, SketchUp, Swift3D, and PV3D use Collada. In this section, you find out how to bring a simple Collada model into PV3D. It’s written in XML and is therefore human-readable. When you first see a Collada file however, you may think it’s not human-readable (it can be a little intimidating at first glance). In comparison to binary format, it’s a cakewalk when it comes to editing it.
187
Part II: Working with Models and Data PV3D contains two Collada parsers: Collada and DAE. The DAE parser is actually an improved version of the Collada parser. One big difference is that the DAE parser supports animation, where the Collada parser does not. Five commonly used scenarios, found in PV3D when working with Collada files, are: ❑
Single Objects with UVMaps
❑
Multiple Objects with UVMaps
❑
Single Objects with Multiple Images
❑
Interactive Objects (or Vehicles) and Environments
❑
Animated Objects
You go through each one in the following section. You start with the Collada parser, as it’s the simpler of the two.
Single and Multiple Objects with Simple Images This is obviously the simplest of all the scenarios. In the example below of the moon orbiting the earth, 3DSMax is used. But the procedure presented is similar for other modeling software packages. An object is created by a modeling program, such as 3DSMax, and a simple image is placed on that object. In 3DSMax this is done using the material editor. Each material and object is given a name so that it can be controlled by the PV3D program (after you’ve imported the Collada file, created by your modeling program). In Figure 5.9, two spheres are created in 3DSMax and given the names earth and moon. Their image files are also given similar names (earth and moon).
Figure 5-9 Once the Collada file is exported from 3DSMax and brought into PV3D, the object can be easily manipulated by referring to the object names contained in the exported Collada file. These names can be viewed by opening the Collada file in an editor such as Dreamweaver or Flex and navigating to the geometries library and geometry tags:
188
Chapter 5: Creating 3D Models Manipulating objects in a Collada file using AS3 is key to creating dynamic game content. The planets are set in orbit using the following steps:
1.
Import the Collada parser: import org.papervision3d.objects.parsers.Collada;
2.
Declare a variable named collada and datatype it to Collada: private var collada:Collada;
3.
Instantiate the Collada object and fill in the appropriate parameters: collada = new Collada(“MoonEarth.DAE”,null,.1);
In this case, the material parameter of the Collada parser is set to null, as the material is imported from the reference inside of the Collada file, found in the images library shown below: ./assets/earth.jpg ./assets/moon.jpg
You actually need to open up the Collada file and make sure that you reference your images correctly. If you don’t, you’ll render a black object with purple lines, which is an indication that your image reference is wrong. You’re almost there; just correct your image reference! Make sure that you select relative paths when exporting your Collada file. Once the images are loaded, the “onColladaDone” method is executed, which sets the planets into place and motion. collada.addEventListener(FileLoadEvent.COLLADA_MATERIALS_DONE, onColladaDone);
The code snippets that accomplish this are explained in the next section.
Setting Planets in Place The planets are accessed in the Collada files using the getChildByName method. The moon is removed from the Collada display object and placed in a pivot display object. The positions of the moon and earth are dictated by the position created in the 3DSMax modeling program. //Get a reference to earth earth = collada.getChildByName(“earth”); //Get a reference to moon moon = collada.getChildByName(“moon”);
(continued)
189
Part II: Working with Models and Data (continued) //Remove collada from the collada object. collada.removeChild(moon); //Create a new pivot point for the moon. pivot = new DisplayObject3D(); //Add the moon to the pivot point pivot.addChild(moon); //Add the pivot point to collada collada.addChild(pivot); //Addcollada to the scene. scene.addChild(collada);
Setting the planets in motion is easily done using the PV3D yaw commands.
Setting Planets in Motion The pivot, which contains the moon, is set into motion around the earth. Then the earth and moon are set into motion around their own pivot. //Rotate the moon around the earth. pivot.yaw(1/3); //Rotate the earth around its axis. earth.yaw(1); //Rotate the moon around its axis. moon.yaw(1);
The results are the moon orbiting around the earth, and both planets rotating around their own axis. The full code can be downloaded from the book’s website. The results are shown in Figure 5.10.
Figure 5-10 Controlling objects inside of a Collada file is a technique that you use often, especially when you start moving game vehicles around your screen.
190
Chapter 5: Creating 3D Models As opposed to programming your own pivots manually in PV3D, you could have set linkage in your 3D modeling code. You do that in the section on Interactive Objects. Next, you learn how to place multiple images on a single object.
Single Object with Multiple Images Multiple images on a single object is the common scenario that you find in SketchUp and Second Life. In 3DSMax, it’s called “applying a multi/sub-object material modifier”. When adding multiple materials in PV3D, you use the MaterialsList.addMaterial method. The MaterialsList acts as an array that holds all the related materials. The code snippet for working with multiple images is as follows:
1.
Import the Collada parser:
import org.papervision3d.objects.parsers.Collada;
2.
Declare the variable collada and datatype it to Collada:
private var collada:Collada;
3.
Set up the MaterialsList Method. This method defines the material to be applied to the multi/ sub object and each image is tagged to the appropriate polygon set using the Collada file.
var materialsList:MaterialsList = new MaterialsList(); materialsList.addMaterial( bitmapFileMaterial1, “image1” ); materialsList.addMaterial( bitmapFileMaterial2, “image2” ); materialsList.addMaterial( bitmapFileMaterial3, “image3” );
4.
Instantiate the Collada class and fill in the appropriate parameters, like so:
collada = new Collada(“my.dae file”, materialsList, scale);
5.
Add your collada to the scene using the addChild method.
scene.addChild(collada);
To illustrate the code snippet above, you create a cubic panorama using a multi/sub-object material modifier found in 3DSMax.
Creating a Cubic Panorama Using Multi/Sub-Objects Adding multiple images to a single polygon object is pretty easy. To create the cubic panorama that’s shown in Figure 5.11, first render a box primitive to the stage (you learn it here in Max, but the process is similar in other modeling applications).
191
Part II: Working with Models and Data
Figure 5-11
The trick to understanding Collada files is to create them in your favorite modeling application and examine them as you make changes. As each change is made in your modeling program, go back and see how your Collada file changed. Experiment, experiment, experiment . . . that’s how the experts do it! It’s also the best way to learn.
Creating Your Pano Object Once you’ve created your pano box, you need to edit it. In Max, that means converting it to an editable poly. The modifier gives you access to the polygon level of your Cube. You go through the steps here, but extensive tutorials on working with 3DSMax are on the book’s website and blog.
1.
After bringing your cube to the stage and converting it to an editable poly, center it so that your pivot point is in the middle of your cube (visually place the center of your cube at 0,0,0).
2.
From your Editable Poly modifier select polygon and then select the six sides of your cube individually and set their Polygon Material IDs (found in the scrollable menu below the poly modifier). This procedure works for any box-modeled entity. After your Material IDs are set, you’re ready to add your images.
3.
Hit the m-key and bring up your material editor. Place the six sides of your cubic panorama in six separate material spheres (see Figure 5.12).
Figure 5-12
192
Chapter 5: Creating 3D Models 4.
Once the six images are on your material sphere, select the 7th sphere and click on its Standard button and when the Material/Map Browser comes up, select Mult/Sub-Object. An IDs menu then appears below your spheres. Drag the appropriate images to the appropriate IDs.
5.
At this point you need to adjust your top and bottom images to align with your pano. Do this by using the UVWmap modifier and Shift+Q to check your render.
6.
To create a pano you need to be inside the cube, which means you need to flip your normal vectors (or once inside your cube you’ll see nothing). Do this by the Normal modifier and make sure that Flip Normals is checked. Your box should turn black since your image normals are facing away from you.
7.
At the moment, your simple cube doesn’t have enough polygons to support your image. When you run it in PV3D, you get image sag (this is not an issue in CS4), so you need to tessellate your cube a few times (twice should work fine). Tessellation just subdivides your cube. You want to do it all at once or you get some weird triangulation.
8.
Finally, export your cube as a Collada file selecting Relative Paths, Normals, and Triangulate. As you’re not doing an animation in this case, make sure that animation is turned off by setting it to false.
Now that you have your Collada pano, it’s time to put it into PV3D.
Putting It into PV3D The code snippet for bringing the Collada pano into PV3D is given below. It follows the previous discussion on MaterialsList very closely. This example below doesn’t use BasicView, but it could have. As you get into more complex examples, you’ll need the code snippet (which was introduced in chapter 2) below: public function PV3DCollada():void { //Set up your Viewport as introduced in Chapter 2. viewport = new Viewport3D(800, 600, false, true); addChild(viewport); //Set the Stage stage.align=StageAlign.TOP_LEFT; stage.scaleMode=StageScaleMode.SHOW_ALL; //Instantiate your Camera, Renderer, and Scene3D scene = new Scene3D(); camera = new Camera3D(); camera.z=0; camera.y=0; renderer = new BasicRenderEngine(); //Set your BitmapFileMaterial make sure your reference is correct bitmapFileMaterial1 = new BitmapFileMaterial(“sky4/sky1/Side1.png”); bitmapFileMaterial2 = new BitmapFileMaterial(“sky4/sky1/Side2.png”); bitmapFileMaterial3 = new BitmapFileMaterial(“sky4/sky1/Side3.png”); bitmapFileMaterial4 = new BitmapFileMaterial(“sky4/sky1/Side4.png”); bitmapFileMaterial5 = new BitmapFileMaterial(“sky4/sky1/Side5.png”); bitmapFileMaterial6 = new BitmapFileMaterial(“sky4/sky1/Side6.png”);
(continued)
193
Part II: Working with Models and Data (continued) //Set Double-Sided to true bitmapFileMaterial1.doubleSided bitmapFileMaterial2.doubleSided bitmapFileMaterial3.doubleSided bitmapFileMaterial4.doubleSided bitmapFileMaterial5.doubleSided bitmapFileMaterial6.doubleSided
= = = = = =
true; true; true; true; true; true;
//Instantiate and load your MaterialsList make sure that it references correctly your Collada images var materialsList:MaterialsList = new MaterialsList(); materialsList.addMaterial( bitmapFileMaterial1, “Side1_png” ); materialsList.addMaterial( bitmapFileMaterial2, “Side2_png” ); materialsList.addMaterial( bitmapFileMaterial3, “Side3_png” ); materialsList.addMaterial( bitmapFileMaterial4, “Side4_png” ); materialsList.addMaterial( bitmapFileMaterial5, “Side5_png” ); materialsList.addMaterial( bitmapFileMaterial6, “Side6_png” ); //Instantiate your collada object and add it to your scene collada = new Collada(“theFlipCubePano2.DAE”, materialsList, .1); scene.addChild(collada); //Set up onEnterFrame event addEventListener(Event.ENTER_FRAME, onEnterFrame); //Define your animation loop function onEnterFrame(e:Event):void { collada.rotationY += .2; renderer.renderScene(scene, camera, viewport); }}
You now move into the wonderful world of vehicles and environments. In addition, you’ll transition from using the Collada parser to using the DAE parser.
Interactive Objects (or Vehicles) and Environments In this section, you find out how to create a basic Flintstone “Barney Rubble car” as an example of an interactive object. The techniques you learn apply to creating any motion-controlled Collada object, such as a boat with a propeller, or an airplane with a propeller and flaps. Building a car requires that you control its motion and wheels. And just as in the planets example, you need to name each of your car parts so you can control them; such as the wheels, car body, and steering mechanism. This provides you with the ability to reference the parts later by their respective names when adding the interactivity code (see Figure 5.13).
194
Chapter 5: Creating 3D Models
Figure 5-13
You create the car by assembling its parts in 3DSMax just as you would put a model together. The important thing is that you give each part a name so you can control them with ActionScript. In this particular example, the following naming conventions are used: ❑
CarBody-your “Barney Log” car body
❑
FRWheel-your front right wheel which links to your steerFR (disk brake)
❑
FLWheel-your front left wheel
❑
steerFR-your front right disc brake which acts as the linkage container for your FRWheel
❑
steerFL-your front left disc brake which acts as the linkage container for your FLWheel
❑
RRWheel-your rear right wheel
❑
RLWheel-your rear left wheel
After creating and naming all the elements of your “Barney car”, export your car as a Collada file. When you look at a Collada file for the first time, you may be shocked. It looks somewhat horrific at first. But actually Collada files are very simple. They’re organized into libraries. Each library typically contains some important aspect of 3D, such as lights, cameras, geometry, animation, physics, and so on. In the case of the “Barney car” above, you’re interested in examining the visual_scenes library. Collada files can be opened in Dreamweaver or Flex. When examining the visual_scenes library nodes you find all the elements created by the 3D exporter:
195
Part II: Working with Models and Data When importing the “Barney car” Collada file into PV3D, you use these names to control your car.
Grabbing Names from Collada Your first task, after importing the Collada file into PV3D, is to grab these names from the Collada file so you can use them in your program: // Steering disc for front wheels steerFR = collada.getChildByName( “steerFR”, true ); steerFL = collada.getChildByName( “steerFL”, true ); steerFR.rotationX=steerFL.rotationX=-90; // Rotation of wheels wheelFR = collada.getChildByName( wheelFL = collada.getChildByName( wheelRR = collada.getChildByName( wheelRL = collada.getChildByName(
“FRWheel”, “FLWheel”, “RRWheel”, “RLWheel”,
true true true true
); ); ); );
These names are stored in appropriate variables and used to control your car. First, you investigate the steering mechanism.
Creating a Steering Mechanism The key to creating a steering mechanism is linking. The steering mechanism is created in 3DSMax by linking the front wheels to their corresponding disc brakes. Since the tires are children of the disc brakes, when the disc brakes are rotated the tires rotate with them. And when the car is moved forward, all the tires rotate together in sequence (see Figure 5.14).
Figure 5-14
Putting this all together in the code below illustrates how the car ’s wheels are maneuvered in PV3D. The steering code is surprisingly simple and consists of only two lines of code: steerFR.rotationY = steer; steerFL.rotationY = steer;
196
Chapter 5: Creating 3D Models As the steering parameter is changed, the rotationY value of the disc is changed, which then changes the angle of the wheel. Next, you learn how to make the wheels roll.
Rolling Wheels and Moving the Car As the speed of the car is increased, the wheel yaw value is also increased, which makes the wheels turn: var roll:Number = speed*.4; wheelFR.yaw( roll ); wheelFL.yaw( roll ); wheelRL.yaw( roll ); wheelRR.yaw( roll ); // Turn your car car.yaw( speed * steer / 500 ); // Move car forward car.moveLeft( speed );
Finally, the car itself is turned using the yaw command and moved forward (or backwards) using moveLeft, where the parameter in the move method is the distance to move in pixels. You may have been expecting moveForward to be used, and in other cases it is. It just depends on the geometry of how your car was created in 3DSMax, and in this case moveLeft actually took you forward. Now, you examine the six possible move methods.
Moving Around the Screen In the DisplayObject3D class there are actually six important move functions, which are used many times to navigate an object in 3D space. The six moves are: ❑
moveForward — translates the display object in the direction it’s facing, that is, its positive
z-axis. ❑
moveBackward — translates the display object in the opposite direction it’s facing, that is, its
negative z-axis. ❑
moveLeft — translates the display object laterally to the left of the direction it’s facing, that is,
its positive x-axis. ❑
moveRight — translates the display object laterally to the right of the direction it’s facing, that is, its negative X axis.
❑
moveUp — translates an object upwards, with respect to the direction it is facing, that is, its positive Y axis.
❑
moveDown — translates an object downwards, with respect to the direction it’s facing, that is, its
negative Y axis. What’s important here is that using these commands allows your object to translate with respect to itself naturally (using its local coordinates), as opposed to its global (or Flash screen) coordinates, which makes sense. But how does it work?
197
Part II: Working with Models and Data public function translate( distance:Number, axis:Number3D ):void { var vector:Number3D = axis.clone(); if( this._transformDirty ) updateTransform(); Matrix3D.rotateAxis( transform, vector ); this.x += distance * vector.x; this.y += distance * vector.y; this.z += distance * vector.z; }
The code above just takes the direction you specify and multiplies it by the distance you specified. But the coordinates are your actual Flash screen coordinates. So the code is actually transforming between your display object local coordinate to your Flash screen coordinate system using the Matrix3D method. Another good application of these functions would be to build a teleporter, like the ones found in Second Life . . . but more on that later. The remainder of the “Barney Car” code can be viewed in the Chapter 5 book code. The remaining part of the “Barney Car” code is keyboard navigation, which can be viewed in the Chapter 5 book code.
Animated Objects To animate or not to animate, that is the question . . . You have two options when it comes to animating objects in PV3D: (1) you can animate them in a 3D modeling software package and attempt to import that into PV3D – and many great websites do this, or (2) you can bring in non-animated 3D objects into PV3D and animate them using AS3. To animate objects in Blender use IPO curves, which readily exports into a PV3D-readable Collada file (see Figure 5.15).
Figure 5-15 IPO stands for InterPOlation and is used to animate objects by creating keyframes as you move your object around the screen. The example in Figure 5.15 is just a simple spinning head and could easily have been animated using AS3. In this example however, it’s used to illustrate how the DAE parser is utilized for this purpose. Once the IPO curves are created in Blender then you export your animation as a
198
Chapter 5: Creating 3D Models Collada file. Animating objects using IPO curves is beyond the scope of this book, but for a good place to learn more about using IPO curves, consult the Blender Noob to Pro manual on Wikipedia found at http://en.wikibooks.org/wiki/Blender_3D:_Noob_to_Pro.
When you’ve exported your Collada file you need to bring it into PV3D using the DAE parser. The steps for using the DAE parser are similar to that of using the Collada parser and are given here:
1.
Import the DAE parser:
import org.papervision3d.objects.parsers.DAE;
2.
Declare collada and dae vars and data type them to Collada and DAE respectively:
private var collada:Collada; private var dae:DAE;
3.
Set up dae and your FileLoadEvent.ANIMATIONS_COMPLETE event listener. You can load your images in two ways; by using the DAE reference to those images, or by using a MaterialsList. The code snippets for both methods are provided.
Loading Images from DAE This method uses the image references already present in your Collada file to load its images. dae = new helperDAE(false, null); dae.addEventListener(FileLoadEvent.ANIMATIONS_COMPLETE, onAnimationsComplete); dae.load(“MyAnimated.DAE”);
Note that helperDAE is a helper class that manages the animation sequence of the Collada file, similar to what the MD2 morphing class does.
Loading Images from a Materials List This method uses a MaterialsList to load the images onto the object. In the snippet below, the materials are first added to a composite material and then that’s added to the object. This is a great way to enhance an image skin, but as mentioned earlier, composite materials result in a double hit on the processor. //Add Materials bitmapFileMaterial1 = new BitmapFileMateria(“MyTexture.png”); wireframe:WireframeMaterial = new WireframeMaterial(0); //Use a Composite Material to enhance your surface composite.addMaterial(bitmapFileMaterial); composite.addMaterial(wireframe); //Add your DAE and Initiate materialsList.addMaterial(composite, “all”); dae = new DAE(true ); dae.scale = 100; dae.load(“MyAnimated.DAE”, materialsList);
199
Part II: Working with Models and Data The fourth step, upon load completion, is to add your dae to the scene using the addChild method: scene.addChild(dae);
Spinning Monkey Head After you’ve animated your object (using IPO curves in this case) and exported your Collada file, you then bring the Collada file into PV3D using the following code snippet. The snippet follows the steps previously discussed: public function MonkeyAnimation() { view = new BasicView(600, 400, false, true, CameraType.FREE); //Add Materials bitmapFileMaterial = new BitmapFileMaterial(“monkeyskin.png”); materialsList = new MaterialsList(); var composite:CompositeMaterial = new CompositeMaterial(); var wireframe:WireframeMaterial = new WireframeMaterial(0); composite.addMaterial(bitmapFileMaterial); composite.addMaterial(wireframe); //Add DAE and Initiate materialsList.addMaterial(comp, “all”); dae = new DAE(true ); dae.scale = 100; dae.load(“SpinMonkey.dae”, materialsList); dae.rotationX=90; dae.rotationZ=180; dae.rotationY=180; //Add the DAE file view.scene.addChild(dae); addChild(view); //Add Load Listener dae.addEventListener(FileLoadEvent.LOAD_COMPLETE, onComplete); }
After everything is loaded, the onComplete method is executed and your animation is rendered. This is an area of study that can easily fill an entire book. The above code snippet is presented as an aid to get you started with animating objects. To see the full code, consult the book’s code download page on the web.
What’s Up with SketchUp? SketchUp is the most promising, and yet most under-utilized resource in all of PV3D. The reason for this is that most SketchUp files need to be hand-edited in order to get them to work in PV3D. Part of the problem is that SketchUp has a lines component, which PV3D doesn’t accept. The result is that PV3D is fed too many parameters at runtime.
200
Chapter 5: Creating 3D Models Besides the fact that SketchUp is extremely easy to use, it also runs on both Mac and Windows machines. SketchUp comes in two versions: SketchUp and SketchUp Pro. However, only SketchUp Pro directly outputs Collada files (.dae), whereas the standard edition of SketchUp only output KMZ (Google Earth) files. There’s a trick to getting Collada files out of KMZ files. Just change the .kmz extension to a .zip extension and unzip it. Within its resource files, you’ll find the Collada file! In Chapter 13 you are presented with one possible scheme for converting SketchUp files into other formats for import into PV3D using the Photoshop 3D extension.
Binary versus XML Before you leave this section, it’s important to understand the trade-offs between using binary data versus XML to encode your models. There’s a misconception out there that a binary format is better than XML, but that’s not necessarily always the case. It depends on three factors: processing time, storage space, and numeric precision. When deciding whether to use binary or XML format, you should keep the following in mind: ❑
If you’re processor-constrained, then binary reduces the overhead of loading data, because stored data exactly matches the in-memory data. But remember that binary data isn’t compressed so it takes up more space.
❑
In most cases, where precision is the same, binary and XML encoding are similar in size. Due to the way that most data is accessed in the pipeline, actual differences in speed are usually unnoticeable when using binary over XML. However, this often depends on the multi-threading capabilities of the processor being used.
❑
If you’re using Collada you have the added benefit of adding binary into your XML structure if it’s needed.
Here’s the real kicker – currently the size of the models being brought into PV3D isn’t large enough to make binary encoding worth it (if it’s not already binary). Most importantly, Collada is cross platform (talks to everything), whereas binary isn’t. It is also important to remember that once your models are loaded, it does not matter what was used to import it. This is because everything is now on the vertex level. Of course, this facet of PV3D is changing, courtesy of Pixel Bender and Alchemy (for more information on Alchemy, refer to Adobe Labs at: http://labs.adobe.com). In the following section you get your first look at creating parsers.
Writing Your Own Parsers This is an area of Papervision3D that’s sometimes frustrating for people. You expect that after creating your model, or finding a really cool one on the web, that it’ll parse in PV3D, and your model will be floating on the web in all its splendor. Unfortunately, more times than not, that’s not the case. To become
201
Part II: Working with Models and Data successful you’ve got to do more than scratch the surface when dealing with parsers. More specifically, you must understand how parsers work and how to write your own when PV3D’s aren’t working for you. If you don’t love XML, you should. It’s a uniform method of communication supported by almost all languages. It’s the web middle man that enables various platforms to talk to each other. Collada is an advanced 3D asset descriptor created in XML to be human-readable and product-independent. Life would be a lot simpler if all elements in the content pipeline could talk to each other in a common format. Collada’s goal is to serve as a standardized transport mechanism between various tools in the content pipeline. It should, in theory, reduce the need for writing a different import and export mechanism for each new application and platform.
Papervision3D and Collada Importing Collada into PV3D, in many cases, just doesn’t work well. However, the potential for great things using Collada and PV3D is there. According to the PV3D Core team, the problem is not with PV3D, but rather with the implementation of the Collada format by modeling applications such as 3DSMax. This is a moot point though, considering the fact that if your team has spent months putting together a model set and can’t import it into PV3D, you’re going to be pulling your hair out. All you have to do is search the PV3D community blog, and you’ll see that this topic has caused a great deal of frustration among members of the community. In reality, Collada is overkill when it comes to most Flash projects. Many developers just want to bring their models into PV3D and then manipulate them using Flash: that only requires vertices, triangles, and UV-data arrays. But Collada offers so much more, and as Flash 3D grows, Collada will most likely become the preferred format. For those who’ve been frustrated by the process and only want to export simple models into PV3D so they can be manipulated using AS3, here are some options:
Simple Blender Exporters It’s commonly believed that Blender doesn’t export a Collada format that’s compatible with PV3D, but that’s not always true as shown in the Monkey animation example earlier. However, when the Collada exporter doesn’t work, you have two other options: an AS3 exporter, and an XML exporter (created by the Author). Both are written in Python. In both cases, a primitive had to be created that receives the data and is rendered in PV3D. Creating an AS3 or XML exporter is a two-step process (here the XML exporter is demonstrated). In step 1, you use Python to grab the needed data: verts, triangle indices, uv data, and normals. In step 2, create an XML primitive to parse the data and create your objects just like you would a sphere or cylinder in PV3D (see Figure 5.16).
Step 1
Step 2
Grab Verts, Indices, UV and convert to XML
Create an XML Primitive that Parses the Data
Figure 5-16
202
Chapter 5: Creating 3D Models Typical XML data for the case of a pyramid is given below; where the XML myVertices tag holds your verts data; the myFaces holds your triangle indices, uv data; and the myParams tag holds position, rotation, and scaling of your object: [1. Vertices] -0.182136,-0.641629,2.526386,-0.182136,-0.641629,0.526386,-2.182136, -0.641629,0.526386,-2.182136,-0.641629,2.526387,-1.182136,1.358371,1.526386, [2. Triangle & UV Data] 0,4,1,0.500000,0.809017,0.000000,1.000000,0.190983,0.500000,0.894427,0.000000,0.447 214,1,4,2,0.190983,0.500000,0.000000,0.000000,0.500000,0.190983,-0.000000,-0.894427 ,0.447214,2,4,3,0.500000,0.190983,1.000000,0.000000,0.809017,0.500000,-0.894427,0.0 00000,0.447213,4,0,3,1.000000,1.000000,0.500000,0.809017,0.809017,0.500000,0.000000 ,0.894427,0.447214,0,1,2,0.500000,0.809017,0.190983,0.500000,0.500000,0.190983,0.00 0000,0.000000,-1.000000,0,2,3,0.500000,0.809017,0.500000,0.190983,0.809017,0.500000 ,0.000000,-0.000000,-1.000000,1 [3. Position, Rotation, & Scaling] 0.025086,0.000000,0.075257,0.000000,0.000000,-0.000000,1.000000,1.000000,1.000000
This data is easily parsed in AS3 using e4x to refer to your XML tags by name. The data format of the XML above is commonly referred to as CSV or comma separated values. And it looks like it sounds; the data is separated by commas. Using the split command, this data is separated into an array so it can be referred to, using indices. As shown in the following code snippet, mySlitVert is your vertices data, and mySlitFace is your triangle and uv data: //Create geometry vertices and faces ve = this.geometry.vertices; fa = this.geometry.faces; //Place your vertex and face data into arrays using the split command var mySlitVert:Array=myData_xml.myPrimSet[0].myVertices.split(“,”); var mySlitFace:Array=myData_xml.myPrimSet[0].myFaces.split(“,”); this.geometry.ready = true; //Load your vertices into a 3D vertex point var j:int; var iVerNum:int=(mySlitVert.length-1)/3; for (j=0;j [2. Vector Normals] 0.89443 0.00000 0.44721 0.00000 -0.89443 0.44721 -0.89443 0.00000 0.44721 0.00000 0.89443 0.44721 0.00000 0.00000 -1.00000 0.00000 -0.00000 -1.00000 [3. UV Data] 0.50000 0.80902 0.00000 1.00000 0.19098 0.50000 0.19098 0.50000 0.00000 0.00000 0.50000 0.19098 0.50000 0.19098 1.00000 0.00000 0.80902 0.50000 1.00000 1.00000 0.50000 0.80902 0.80902 0.50000 0.50000 0.80902 0.19098 0.50000 0.50000 0.19098 0.50000 0.80902 0.50000 0.19098 0.80902 0.50000 [4. Triangle Data]
0 0 0 4 0 1 1 0 2 1 1 3 4 1 4 2 1 5 2 2 6 4 2 7 3 2 8 4 3 9 0 3 10 3 3 11 0 4 12 1 4 13 2 4 14 0 5 15 2 5 16 3 5 17
205
Part II: Working with Models and Data The visual_scenes library contains translation, rotation, scaling, and naming information: 0.02509 0.07526 0.00000 0 0 1 0.00000 0 1 0 -0.00000 1 0 0 0.00000 1.00000 1.00000 1.00000
Understanding the data output and how it’s organized is essential to building your own Collada parsers. If you pick up a book on Collada, you may be blown away by how complicated it sounds at first, but in reality it’s a very simple format. Referring to the code above, here’s how it works: ❑
Collada documents are organized into a sequence of libraries. Example libraries include: animation, camera, effect, light, material, physics, controller, geometry, and more. In Chapter 13, you’ll be adding a library for CS4 Flash animation as well.
❑
The library that you’re presently interested in is the geometry library, which contains all the information that describes the object.
❑
The element is the parent of the elements, which contain the raw data for vertices, normals, uv data, and triangle indices.
With this information, you can build a simple Collada parser for PV3D that works with various modelers, including 3DSMax, Blender, and SketchUp. Once your object is parsed and its vertices are set into arrays, a texture needs to be applied to it. Texturing models in PV3D requires some knowledge of image processing.
Processing Images (and Pixel Bender) Learning how to process good-looking textures for your 3D objects is an essential ingredient to realisticlooking models. Without textures, your 3D models are lifeless, and in PV3D, they don’t even exist: as the image classes conduct the primitive construction. If you’re going to learn only one image-processing software application, your application of choice should be Photoshop. With the release of Creative Suite 4, you now have a hefty 3D component. The open source alternative to Photoshop is called Gimp. A great many great books are available out there on processing images for models. Additionally, resources for learning Photoshop and Gimp are provided on this book’s website and blog. However, when it comes to image processing in AS3, there’s a new kid on the block, called Pixel Bender. You’ll be doing a lot of work with Pixel Bender in the coming chapters.
What’s Pixel Bender? Pixel Bender is arguably the greatest thing that’s happened to Flash3D to date. It’s a tool that works with CS4, and lets you create your own pixel shaders or bitmap filters, but not like the slow ones in the previous version of Flash. Instead, Pixel Bender works at the machine level. Pixel Bender is a separate
206
Chapter 5: Creating 3D Models program that has its own IDE and C-like programming language, which is compiled and imported into Flash. Its single purpose is to modify the value of a pixel, and it does so incredibly fast. Pixel Bender takes advantage of multi-threaded, parallel processing to render an entire bitmap almost instantly, rather than processing each individual pixel within a given bitmap one at a time. This single characteristic of the rendering engine makes Pixel Bender the fastest gun in the west, so expect to see amazing things as it makes its way into Flash3D. You can use Pixel Bender to: ❑
Create your own custom filters
❑
Create your own blend modes
❑
Create your own custom fills
❑
Do generic number crunching
In the last chapter, you already saw the importance of bitmapFill. In this section, you discover how to create and apply your own pixel shader fill to Papervision3D and CS4 objects.
Getting Started with Pixel Bender Getting started with Pixel Bender is easy. First, go to the Adobe labs website and download Pixel Bender at http://labs.adobe.com/technologies/pixelbender/ The installation is pretty straightforward; just follow the installation instructions provided on the webpage. The next step is very important: when pixel bender downloads, it includes a docs folder, which has two very important documents: PixelBenderGuide and PixelBenderReference (see Figure 5.18). These guides help demystify the Pixel Bender process and save you hours of work, so let’s get started:
Figure 5-18
207
Part II: Working with Models and Data After clicking on the pixel bender icon, a very simple interface appears, consisting of three UI panels: ❑
Image View — upper panel for loading images and examining shaders.
❑
Source Editor — lower panel for writing code.
❑
Filter Parameter UI — right panel for testing parameter “slider” effects. This allows you to see how your filter is affecting your image in real time.
Writing a Pixel Bender Filter The Pixel Bender language is not AS3. It’s a low level scripting language that’s similar to C, and it is very simple to use. Creating a filter or shader in Pixel Bender is accomplished through the following three steps:
1. 2. 3.
Create a new, or open an existing, Pixel Bender filter. Select the input images you’d like to use. Click Run and use the parameter options to see your filter in action.
You now go through the details of accomplishing each step.
Step 1 When you have Pixel Bender up, go to the file menu and click New Kernel. This populates your source code window with the simplest filter you can write – one that does nothing, but has all the essential elements of a pixel filter or shader. [Part 1 Identifier and Kernel]
//Kernel Definition kernel NewFilter < namespace: “Your Namespace”; vendor: “Your Vendor”; version: 1; description: “your description”; > [Part 2 Your filter code] { input image4 src; output pixel4 dst; void evaluatePixel() { dst = sampleNearest(src,outCoord()); } }
208
Chapter 5: Creating 3D Models A Pixel Bender program is divided into two parts. The first part is your version identifier that includes your kernel definition followed by the name of your filter, and some meta data about your filter. The next part, contained between the curly braces, is your filter code. In the preceding pixel code are two important commands: input and output. The input grabs your source image and has – in this case – the data-type image4, which means it has four color channels: red, green, blue, and alpha. Each channel is a 32-bit floating point value. The output has the data-type pixel4, which means you’re outputting a four-channel pixel with a 32-bit floating point value. So your input is an entire image (thus image4), but your output is a single pixel (thus pixel4), and the code is run for every single pixel in your image with the input and output variables named src and dst for “source” and “destination” respectively. A noteworthy point is that Pixel Bender supports four data types: integer, float, pixel, and Boolean. Pixel Bender also has matrix support and a number of built in mathematical functions, such as sine, cosine, arctan, and so on. Consult the Pixel Bender reference documentation for more information on the functions available from within the Pixel Bender scripting language. Next, the heart of your application is the evaluatePixel function. The code in this function is run for every single pixel in your image. This is where your processing algorithm goes: evaluatePixel() { dst = sampleNearest(src,outCoord()); }
In this example, you’re only setting the destination pixel to the current value of your present pixel using the sampleNearest function. This method takes two parameters: your source image, and the outCoord property, which gives you the current x and y values of the pixel you’re currently evaluating. Therefore, sampleNearest returns the color of the pixel4 value of the present x, y coordinate of your src. At the heart of Pixel Bender is the outCoord() property, which is the midpoint of the output pixel.
Step 2 Go to the File menu and load an image. By default a samples images folder opens up, but you can pull any image from your hard drive. It’s not the images that matter, but the processing; remember you’re creating an algorithm that processes images. But what’s cool is that you can see how your pixel algorithm is affecting your photo as you make changes to the processing algorithm.
Step 3 You now write a simple algorithm, which enhances the green channel of your image. Test it using the run button found in the Build menu. Within the evaluatePixel function, make the following changes: evaluatePixel() { pixel4 colorChannel = sampleNearest(src,outCoord()); colorChannel.g = colorChannel.g*8.0; dst = colorChannel; }
209
Part II: Working with Models and Data In the code, a new pixel4 variable is created called colorChannel and is set equal to the present (x,y) pixel position being evaluated. The green value (grabbed by using dot syntax “.g”) of your colorChannel.g is then multiplied by 8.0 (8.0 is used instead of 8 as you’re working with floating point numbers). You then set your destination (dst) equal to your adjusted colorChannel. Hitting the run button gives you an enhanced green image. After creating your Pixel Bender, save it as a PBK file, which can then be loaded and played as a filter by the Flash player. You now create a variable parameter to control the amount of green you enhance your green channel with.
Adding Parameters Using the parameter command allows you to add variables to your filters. In the following code, you learn to adjust your green color channel using a slider bar. The parameter is created by declaring a parameter (greenParam), then setting its min, max and default, and multiplying it by your colorChannel variable: parameter float greenParam < minValue:1.0; maxValue:10.0; defaultValue:1.0; >; void evaluatePixel() { pixel4 colorChannel = sampleNearest(src,outCoord()); colorChannel.g = colorChannel.g*greenParam; dst = colorChannel; }
Because greenParam is a parameter, when you bring this filter class into Flash, you can control it using ActionScript!
Typical Point Processes In filter language, a point process modifies your source pixels, one pixel at a time. The values of your new pixels are independent of each other. Using relatively simple algorithms, you can make some very significant changes, such as brightness, color, balance, and contrast. A whole host of standard pixel transformations exist, which you now take a look at.
210
Chapter 5: Creating 3D Models
Grayscale Converting an image from color into shades of gray is one of the simplest image-processing tasks. It’s accomplished by averaging the three color channels, and setting each channel to that average. The code looks like this: pixel1 shadeVal=(colorChannel.r+colorChannel.g+colorChannel.b) /3.0; colorChannel.r = colorChannel.g = colorChannel.b = shadeVal;
Complement To create the complement of an image, subtract each pixel color channel from 255 or in the case of Pixel Bender, subtract from 1.0, since RGB values in Pixel Bender have a value from 0 to 1, which represents an RGB channel value of 0 to 255. The Pixel Bender code is shown here: colorChannel.r = 1.0-colorChannel.r; colorChannel.g = 1.0-colorChannel.g; colorChannel.b = 1.0-colorChannel.b;
Brightness To add to the brightness of a color, you add the percentage increase in brightness times the difference in its complement to each pixel’s component. . Check out the algorithm below: colorChannel.r += brightParam*(2.0-colorChannel.r); colorChannel.g += brightParam*(2.0-colorChannel.g); colorChannel.b += brightParam*(2.0-colorChannel.b);
Note. A factor of 2.0 was introduced in the code above instead of 1.0. This is a hack solely for the purpose of visual appeal).
Exposure Exposure is similar to brightness, but you split your color channel into RGB and alpha and apply a pow function to your RGB color channel: dst.rgb = pow( colorChannel.rgb, float3(1.0-exposureParam)); dst.a = colorChannel.a;
Color Balance Color balance is very similar to brightness, except that you adjust each color individually: colorChannel.r += redParam*(1.0-colorChannel.r); colorChannel.g += greenParam*(1.0-colorChannel.g); colorChannel.b += blueParam*(1.0-colorChannel.b);
211
Part II: Working with Models and Data
Binary Contrast Contrast measures the difference between two colors. Binary contrast adjusts your color values to only two possible values: black or white. In this case, you just calculate the grey scale and convert it to black or white depending on a shade parameter: pixel1 shadeVal=(colorChannel.r+colorChannel.g+colorChannel.b)/3.0; if(shadeVal>shadeParam) { colorChannel.r = colorChannel.g = colorChannel.b = 1.0; } else{ colorChannel.r = colorChannel.g = colorChannel.b = 0.0; }
Simple Pixelate Simple Pixelate uses the floor function to widen your color box region. It’s really just a trick, but demonstrates a good use of the floor function: float2 pixelCorner = floor(outCoord()/dimAsFloat); pixelCorner = dimAsFloat*pixelCorner; outputPixel = sampleNearest(inputImage, pixelCorner);
Simple Blur A blur filter works by including a bit of surrounding pixel color into the color of your current pixel. This is typically accomplished by using some type of weighting function that drops off over distance, such as a Gaussian distribution. You design a simple blur filter using a linear drop off. The algorithm below adds two pixel colors beyond your center pixel and drops off linearly: color = color + 0.75*color(blurParam/2) + 0.25*color(blurParam) The algorithm works well for a blurParam value between 0 and 10, but then breaks down. To improve it, you need to add the necessary terms. . The Pixel Bender snippet is shown here: float2 pos = outCoord(); pixel4 color = sampleNearest(src,pos); color+=0.75*sampleNearest(src, pos+float2(0.5*blurParam, 0))+0.25*sampleNearest(src, pos+float2(blurParam, 0)); color+=0.75*sampleNearest(src, posfloat2(0.5*blurParam, 0))+0.25*sampleNearest(src, pos-float2(blurParam, 0)); color+=0.75*sampleNearest(src, pos+float2(0, 0.5*blurParam))+0.25*sampleNearest(src, pos+float2(0, blurParam)); color+=0.75*sampleNearest(src, pos-float2(0, 0.5*blurParam))+0.25*sampleNearest(src, pos-float2(0, blurParam)); dst = color/5.0;
212
Chapter 5: Creating 3D Models
Sharpen The sharpen filter is similar to the blur filter in that you must displace the colors (created by Ryan Phelan on Adobe Pixel Bender exchange). After displacement, you must add and subtract them in such a way that you expose the outlines: float4 left = sampleLinear(src, coord-float2(radius, 0.0)) * amount; float4 right = sampleLinear(src, coord + float2(radius, 0.0)) * amount; float4 top = sampleLinear(src, coord-float2(0.0, radius)) * amount; float4 bottom = sampleLinear(src, coord + float2(0.0, radius)) * amount; dst.rgb += (top.rgb); dst.rgb -= (bottom.rgb); dst.rgb += left.rgb; dst.rgb -= right.rgb;
The filter uses two parameters, radius and amount: radius increases the displacement, and amount increases the amount of color displacement.
Simple Point Light The equation places your light at the center position, since at that position there is a singularity in your equation (coord — center = 0). At that singularity, everything goes white. The attnDecay variable determines the spread of the light’s decay across your image. The attnMult variable multiplies your light source by a value and the brightness variable exponentiates the entire expression (see Figure 5.19).
Figure 5-19 The Pixel Bender code snippet is given here: float2 outcoord = outCoord(); float attn = pow(attnMult/pow(distance(outcoord, center), attnDecay), brightness); dst = attn* sampleNearest(src, outcoord);
213
Part II: Working with Models and Data
Bloom Brightness Bloom brightness is used in Away3D and was developed by Der Schmale. It filters the bright parts of the image and caps the darker parts at black: float4 current = sample(src, outCoord()); if (length(current.xyz) < threshold) { dst = pixel4(0.0, 0.0, 0.0, 1.0); } else { current.xyz *= exposure; dst = current; }
The code uses two parameters: threshold and exposure. When the distance is less than the threshold, your pixel RGB channels are set to zero. If it’s greater than the given threshold, your exposure kicks in. You’ll see this again when you add Pixel Bender to PV3D.
Multiple Image Processing Multiple image processing involves combining two or more images in some way. A kernel can take any number of input images, each of which can have a different number of channels. There are a number of standard multiple-image algorithms. Here are a couple of them:
CrossFade Crossfade grabs the pixels from both images and combines them proportionately using an intensity
parameter: // Grab the pixel values from both images float4 topPixel = sampleNearest(topImage, outCoord()); float4 bottomPixel = sampleNearest(bottomImage, outCoord()); intensity)*topPixel+ bottomPixel*intensity;
dst = (1.0-
An intensity of 0 reveals your first image and an intensity of 1 reveals the second one. Anywhere in between 0 and 1 is a combination of the two, weighted proportionately.
Subtraction and Addition To subtract images just subtract the color components of the corresponding images: // Grab the pixel values from both images pixel4 topPixel = sampleNearest(topImage, outCoord()); pixel4 bottomPixel = sampleNearest(bottomImage, outCoord()); dst.rgb=topPixel.rgb-(1.0-intensity)*bottomPixel.rgb; dst.a=1.0;
214
Chapter 5: Creating 3D Models Similarly, average is just adding the two corresponding image pixel values together and dividing by 2: dst.rgb=(topPixel.rgb-(1.0-intensity)*bottomPixel.rgb)/2.0;
Subtraction has traditionally been used for edge detection and addition for noise reduction. You now learn how to bring Pixel Bender into Flash.
Bringing Pixel Bender into Flash Before you get started on bringing Pixel Bender into Flash, you should be aware of a few limitations: ❑
Flash player always uses 1x1 square pixels.
❑
Pixel Bender images have 32 bits per channel, but graphics in Flash 10 only has 8 bits per channel. When the kernel is run in Flash, the input image data is converted to 32 bits per channel and then converted back to 8 bits per channel when kernel execution is complete.
❑
Arrays, region function, custom libraries, dependent values, and graph language aren’t supported in Flash.
❑
Control structures other than if and else (for example, Loops) aren’t supported.
If you get an error while trying to import a Pixel Bender filter to Flash it most likely means you’ve attempted one of the unsupported items above.
Exporting Pixel Bender After creating and testing your filter, go to the Pixel Bender file menu and click on File and then Export Filter for Flash Player. That exports your filter as a Flash filter. Notice your file extension has changed. Pixel Bender has a file extension of .pbk and Flash filters have a file extension of .pbj. You can now download this filter into Flash and use it just like any other filter you would use in Flash. The key point is that it doesn’t result in the same processor load that the stock filters in Flash do.
Adding Pixel Bender to Papervision3D At the time of this writing, PV3D was in need of a CS4 overhaul. In this section, you find out how to put Pixel Bender into PV3D, by creating an algorithm that shades a PV3D surface. Adding Pixel Bender to Papervision3D is a three-step process:
1. 2. 3.
Creation of a Pixel Bender Filter Creation of a Pixel Bender BitmapFileMaterial Object Creation of a Pixel Bender BitmapMaterial Object
However, before you start coding, make sure that you’ve switched to the Flash 10 player, otherwise you’re going to get an error.
215
Part II: Working with Models and Data Step 1: Creating a Pixel Bender Filter Adding dynamic shading to PV3D first requires that you create a more efficient light source, such as the simple point light filter that we presented in the previous section. The only problem with that filter is that there are so many exponent calculations, so you’re going to simply throw out all the exponents. You can use inverse distance to mimic exponential behavior and thus eliminate all your exponents; a huge processor saver. Your new algorithm is shown below: The Pixel Bender algorithm is easily implemented using the code below: float2 outcoord = outCoord(); float attn = (brightness/((distance(outcoord, center)+radius))); dst = attn* sampleNearest(src, outcoord); dst.a=1.0
Also, note that the alpha component is split off and set to one. This keeps the image from losing its alpha channel as you reduce the brightness.
Step 2: Creating a Pixel Bender Bitmap File Material Class Next you must create a bitmap file material class for Pixel Bender. This can be done by simply making a copy of PV3D’s BitmapFileMaterial class and renaming it to BitmapBendMaterial. Then, after you change the constructor and class name to match, use this class to extend the BitmapPixelMaterial class. You create the BitmapPixelMaterial in the following step: BitmapBendMaterial extends BitmapPixelMaterial
Step 3: Creation of a Pixel Bender Bitmap Material Class Make a copy of the PV3D BitmapMaterial class and name it BitmapPixelMaterial. Add the appropriate import statements and the PBJ and image embed methods and create a shader as shown below: //Embed your images [Embed (source=”filters/livelyLight.pbj”, mimeType=”application/octet-stream”)] private var ShaderClass:Class; [Embed (source=”assets/images/gainesHouse512.jpg”)] private var myImage:Class; private var shader:Shader
Next, incorporate the code below to get your shader and image working together: shader = new Shader(new ShaderClass()); shader.data.center.value = [400, 306]; shader.data.brightness.value = [150]; shader.data.radius.value = [100]; var image:Bitmap = new myImage(); image.filters = [new ShaderFilter(shader)]; shader.data.src.input = image.bitmapData
216
Chapter 5: Creating 3D Models Finally, swap out the beginBitmapFill with the beginShaderFill method (which contains a matrix method shown in the following code). Be sure to check out the book’s website video on this topic for further explanation. graphics.beginShaderFill(shader,_localMatrix)
The results of using the new shading are illustrated in Figure 5.20.
Figure 5-20 Figure 5.20 shows a dynamically shaded house using Pixel Bender. The house on the left shows no contrast and is washed out, whereas the house on the right appears much more realistic through the dynamic shading you just implemented. The big advantage here is that you don’t have to “texture bake” your images. Texture baking applies lighting non-dynamically through an additional image-processing step. It’s more efficient than other lighting because it’s baked into the texture. However, once it’s baked into the texture, it’s static and is therefore not capable of changing dynamically based on changes to the scene, camera, or viewport properties. For example, if you wanted a sun to rise and set on your model house, your contrast (or shading) can change dynamically to correspond with the position of the sun at any given moment. This is a facet of 3D that makes 3D scenes appear so realistic. Check out the book’s chapter code for the working demo. You can also extend your simple light to create multiple lights by adding additional terms.
Creating a Multiple Light Source Having multiple lights in PV3D is a desirable effect. However, after going through the Lightmap classes in Chapter 4, you may conclude that it’s impossible without a major rewrite of PV3D. With Pixel Bender, all you need to do is add another term to your PBK filter.
217
Part II: Working with Models and Data Simply add another term to your Pixel Bender light equation as shown below: The preceding code only shows two lights, but you can have as many lights as you want just by adding additional light terms. The Pixel Bender code is provided here: float2 outcoord = outCoord(); float attn = (brightness1/((distance(outcoord, center1)+radius1)))+(brightness2/ ((distance(outcoord, center2)+radius2))); dst = attn* sampleNearest(src, outcoord); dst.a=1.0;
Using Flash 10 reduces the amount of code you need to write significantly, which is why you can create multiple light sources with only five lines of code.
Animating Your Light Sources Once you’ve set up your dynamic Pixel Bender shader, then you can animate it fairly easily. Here are the steps for animating your lights: ❑
Increment your oscillation parameter with osc++
❑
Calculate sine and cosine based upon your incremented osc
❑
Update your light positions
❑
Apply the update to your image
The documented code is given here: private function loop(e:Event):void{ //Increment your oscillation parameter osc++; //Calculate sine and cosine var cos:Number=150*Math.cos(osc/10); var sin:Number=150*Math.sin(osc/10); //Update your light positions shader.data.center1.value = [sin+400, cos+180]; shader.data.center2.value = [cos+200, sin+180]; //Apply the update to your image image.filters = [filter]; //Rotating your image holder holder.rotationY+=.25; }
Dynamic shading in PV3D is difficult, but in Pixel Bender it’s a breeze!
218
Chapter 5: Creating 3D Models
Summar y Wow, that was a lot of material for just one chapter! You started with modeling and ended up with Pixel Bender. Regardless of how PV3D changes over time, the principles presented in this chapter will be around for a while. You’ll still need parsers to bring in vertex data regardless of your software environment. And Pixel Bender, the new kid on the block, will obviously become the cornerstone of new 3D packages that come about as the Adobe Flash Platform continues to evolve. For further explanation of the material presented in this chapter, be sure to consult the book’s website and blog. Those resources have been created to help you become a 3D AS3 ninja and master this material.
219
Working with Par ticle Systems No 3D animation is complete without a particle system. Fire, smoke, water, a swarm of bees, or flock of birds can all be created using particles. Particles add an element of interactive realism. If you’re on a star quest mission and your ship gets hit with a plasma beam, your CGI (computer generated imagery) is using particles to create the plasma. Each particle has its own set of physical parameters such as position, velocity, and acceleration, which requires that PV3D incorporate physics into its classes. PV3D’s particle system, at present, isn’t very robust. In this chapter you bring it up a notch using a few new OOP principles and a little bit of Newtonian physics. You’ll build a 3D particle system from scratch; learn how to create particles in PV3D, and create a particle glowworm in Flash CS4. You get your particles interacting with video, and exploding. And finally, you find out how to use bill-boarding to create large particle systems; how to create a scanning particle system, and how to incorporate the FLINT particle system into PV3D. So, what’s a particle system anyway?
Par ticle Systems in PV3D A particle system is a large collection of similar particles acting in a similar way, like rain drops, smoke, and bees to name a few. PV3D itself can be thought of as a particle system (where the display objects are treated as particles). Also, like a particle system, PV3D keeps track of many points in space (vertices for example) and updates them periodically through rendering. Particle systems typically have the following properties: ❑
Initial Particles are generated.
❑
Each particle has unique attributes.
Part II: Working with Models and Data ❑
Particles have a life time (or designated space) and afterwards are removed or reused.
❑
Particles are governed by scripts (classes that determine their behavior).
❑
Particles are rendered.
Particles add life to your scenes – fire, smoke, vapor, and explosions. And if you want your 3D avatar to enjoy sitting next to her virtual fireplace for a cozy evening, then light that fireplace up using particles. Understanding how particles are created and react will greatly enhance your understanding of PV3D.
Creating Particles in PV3D PV3D has a very limited particle system. Its ParticleMaterial class only has two particles: a circle and a square, each with an alpha and color property. Like other special materials, the ParticleMaterial class is found in the org/papervision3d/materials/special folder. But this class alone will not create particles in PV3D. You need the ParticleField class (found in org/papervision3d/objects/ special), which uses polymorphism to grab your particle material and add number, size, and dispersion properties to it. This isn’t enough to create particles in PV3D though. You need one more class: the Particles class (found in org/papervision3d/core/geom), which lets you add and remove particles from the stage.
Particle Material Create a single particle
Particle Field Disperse particles
Particle Add and remove particles
Figure 6-1 The steps and code for creating a particles system in PV3D is given here:
1.
Import the required classes.
import org.papervision3d.materials.special.ParticleMaterial; import org.papervision3d.objects.special.ParticleField;
2.
Declare a particle material, a particle field variable, and data type.
private var particlemat:ParticleMaterial; private var stars:ParticleField;
3.
Instantiate your particle material and particle field and then add it to your scene by using the addChild method.
particlemat = new ParticleMaterial(0xffffff,1,ParticleMaterial.SHAPE_CIRCLE); stars = new ParticleField(particlemat, 300,3, 2000,2000,2000); scene.addChild(stars);
As can be seen from the code above, the constructor function for the particle material contains three parameters: color, alpha, and shape; at present you’re limited to only two shapes but in the following section, you extend that to include a few more.
222
Chapter 6: Working with Particle Systems ParticleMaterial(color, alpha, shape);
The Particle Field constructor contains the particle material, number of particles to create, size, and x, y, and z dispersion areas. ParticleField(particle material, number, size, fieldWidth, fieldHeight, fieldDepth)
Start with putting stars (your two particles and more) in a skybox.
Putting Stars in a Skybox Because PV3D only has two particles in its class, you can only generate a two-star universe (a circle and a square), which would be a pretty boring place. So first you need to extend the number of stars in PV3D and rewrite the ParticleMaterial class to make it a little more efficient. Currently, you only have a square and circle particle to put in your skybox so you add a star, gear, wedge, poly, and burst to make your universe a little more complete. These simple shapes are easily drawn in Flash using the following two steps:
1.
Open up PV3D’s ParticleMaterial class and in the public class add the following static properties: public public public public public
2.
static static static static static
var var var var var
SHAPE_STAR:int = SHAPE_GEAR:int = SHAPE_WEDGE:int = SHAPE_POLY:int = SHAPE_BURST:int =
2; 3; 4; 5; 6;
In the drawParticle method, get rid of the else if statements and add a switch case. In the code below only the code for the Star shape is shown, the other shape codes can be found in the Chapter 6 book code (in the PV3DParticles folder in the ParticleMaterials class):
switch(shape) { case SHAPE_SQUARE: graphics.drawRect(renderrect.x, renderrect.y, renderrect.width, renderrect.height); break; case SHAPE_CIRCLE: graphics.drawCircle(renderrect.x+renderrect.width/2, renderrect.y+renderrect.width/2, renderrect.width/2); break; case SHAPE_STAR: var points:int=6; var innerRadius:Number=renderrect.width/10; var outerRadius:Number=renderrect.width*2; var count:int = Math.abs(points); if (count>=2) { // calculate distance between points var step:Number = (Math.PI*2)/points;
(continued)
223
Part II: Working with Models and Data (continued) var halfStep:Number = step/2; // calculate starting angle in radians var start:Number = (20/180)*Math.PI; graphics.moveTo(renderrect.x+(Math.cos(start)*outerRadius), renderrect.y(Math.sin(start)*outerRadius)); // draw lines for (var i:int=1; i 40) { myOsc++; myParticle.velocityX = 10*Math.cos(myOsc/10)*Math.cos(myOsc/10); myParticle.velocityY = 10*Math.sin(myOsc/10); myParticle.velocityZ = 10; myParticle.x = (stage.stageWidth/2)*Math.cos(myOsc/100)*Math .cos(myOsc/100); myParticle.y = (stage.stageHeight/2)*Math.sin(myOsc/100); myParticle.z = (1000); myParticle.scaleX=myParticle.scaleY=1; myParticle.alpha=1; myParticle.particleLife=myLifeLived; myParticle.scaleX=myParticle.scaleY=3; } } sortZ(); } //Sort function function sortZ():void { particleArray.sortOn(“z”, Array.DESCENDING | Array.NUMERIC); for(var i:uint = 0; i < particleArray.length; i++) { var myParticle:smallParticle = particleArray[i]; setChildIndex(myParticle, i); } } //Fourth Part, set event listener to update particles on enter frame addEventListener(Event.ENTER_FRAME, myLoop);
A number of very important things are happening in the code above, which are generic to all particle systems no matter how many dimensions. Only the prescribed number of particles can be created, and once those particles exceed their lifetime or bounding box, they’re recycled. Central to particle systems is the particle array, which you must iterate over and continually update using the myUpdate method, created previously in the Particle3D class.
231
Part II: Working with Models and Data Key Particle Concepts There are a number of key properties that you commonly encounter when working with particle systems. We describe them briefly below:
Emitter Creating an emitter is simple – you just tell the particles where to be created. In the code, we generate them at the mouse pointer with a small variation on velocity. myParticle.ypos = mouseY-vpY; myParticle.xpos = mouseX-vpX;
Regenerator When a particle has outlived its lifetime we regenerate it by sending it back to the source. Regenerating particles is usually preferable due to the way in which Flash deals with memory resources. In PV3D particle sorting is done for you automatically, but in CS4 you must create a sorting algorithm.
z-Sorter As you’re dealing with multiple particles in CS4 you must sort them based on their z-position, otherwise they’ll overlap incorrectly. The sorter presented in the code above is one of many possible z-sorters. Later in the book (Chapter 16), you learn how to create a spherical image ball, for which you’ll use a different sorter.
Velocity Velocity is added by incrementing it in the loop. To get a 3rd dimension all you have to do is add a z component. myParticle.xpos += myParticle.vx; myParticle.ypos += myParticle.vy; myParticle.zpos += myParticle.vz;
Gravity Gravity can get rather complex, but in many of the animated examples presented all you have to do is add a little more value to your y velocity, and your particle falls in the y-direction. Iterating the y-velocity creates gravity. For example, to get smoke you just change the sign of the gravity term and iterate upwards. myParticle.vy += gravity;
Bounce Bounce adds a bit of physical reality to your particle. When the particle hits the floor, bounce is created by reversing velocity and doing a decimal multiplication on velocity.
232
Chapter 6: Working with Particle Systems myParticle.bounceNum++; myParticle.vy *= bounce =-.6;
In the example above, bounce is equal to -.6, which reverses the velocity and reduces it exponentially. This list can go on and on, depending on your particular scenario.
Lifetime or Bounding Box Particles have a limited existence or lifetime. The lifetime of a particle can be based on geometry or time. In the glow worm code, you limited your particle’s lifetime by using the particleLife variable. When your particle exceeded its particleLife variable of 20 (found in the if statement), it was regenerated (or reinitialized). But you could have limited your particle’s lifetime by using a bounding box as well. When it hit the bounding box it would be regenerated. Next, take your Particle3D class, make a few changes, and use it to make things explode in 3D!
Making Things Explode (in Flash CS4) Exploding an object is serious gaming fun. There’s just something about the human psyche that loves to see things blow up. The first step in making things explode is to add a spin (x, y, z) property to your Particle3D class. You can accomplish this by following these three steps:
1.
To keep things straight, rename your Particle3D class to ParticleBlowUp and add a spin(x, y, z) property
public var spinX:Number; public var spinY:Number; public var spinZ:Number;
2.
In the constructor function, set the spin(x, y, z) equal to zero.
spinX = 0; spinY = 0; spinZ = 0;
3.
In your animation method myLoop, update the rotation of the particle by iterating the spin(x, y, z) amount.
this.rotationX += spinX; this.rotationY += spinY; this.rotationZ += spinZ;
That’s all you have to do to your Particle3D (now named ParticleBlowUp) class. In the discussion above, you added the new spin(x, y, z) property to your ParticleBlowUp class. The process of adding any property to your particle class is always the same. Add your property, initialize it in your constructor, and add its rule to your animation loop as shown in Figure 6.5.
233
Part II: Working with Models and Data Add A Property
Initialize it in your Constructor or Init Method
Add its rule to your Animation Loop
Figure 6-5
This is so cool, since as mentioned earlier, it’s so easy to add as many properties as your particles need. However, you’re only halfway done. In this case, just adding spin isn’t enough. As things are now, only a single individual particle will spin. To get your particle system to explode you need to transfer a multi-part movie clip into your Particle class (using polymorphism). Then iterate over the elements of that movie clip, so that you can spin those elements off into space in different directions, simulating an explosion. To do this, make the following changes to your ParticleBlowUp class shown here:
1.
Remove the extends Sprite statement from your ParticleBlowUp class; this allows you to bring an object into your ParticleBlowUp class.
2.
Create a public property obj and data type it as a DisplayObject:
public var obj:DisplayObject
3. 4.
Add obj to your constructor method, declare obj as an Object in your constructor method. Finally, change all the this keywords to object so that the transformations are applied to the object passed in through the constructor.
The completed code is provided: package org.lively3d.particles { import flash.display.*; //Particle Parameters public class ParticleBlowUp { public var velocityX:Number; public var velocityY:Number; public var velocityZ:Number; public var gravity:Number; public var friction:Number; public var fade:Number; public var autoRotate:Boolean; public var spinX:Number; public var spinY:Number; public var spinZ:Number; public var object:DisplayObject;
234
Chapter 6: Working with Particle Systems public function ParticleBlowUp(obj:DisplayObject) { //Set Parameters object = obj; velocityX = 0; velocityY = 0; velocityZ = 0; gravity = 0; friction = 1; fade = 1; spinX = 0; spinY = 0; spinZ = 0; } //Update Parameters public function myUpdate():void { velocityX *= friction; velocityY *= friction; velocityY += gravity; object.x += velocityX; object.y += velocityY; object.z += velocityZ; //Update rotation object.rotationX += spinX; object.rotationY += spinY; object.rotationZ += spinZ; } } }
Next, open up Flash and make a few movies (3D card houses) and make sure that those movies are made up of various movies. You iterate over these pieces to simulate the explosion. Figure 6.6 shows three card houses that explode when you click on them. To get this to happen, you need to give each house an appropriate instance name (house1_mc, house2_mc, house3_mc).
Figure 6-6
235
Part II: Working with Models and Data You then attach listeners to each one of these instance names, so when you click on them the explode method is executed. //Set your click listeners cards1_mc.addEventListener(MouseEvent.CLICK, explode); cards2_mc.addEventListener(MouseEvent.CLICK, explode); cards3_mc.addEventListener(MouseEvent.CLICK, explode); text_mc.addEventListener(MouseEvent.CLICK, explode);
The explode method is pretty straightforward; each particle is given a random velocity and spin (in x, y, z), and gravity is set to 1, which brings the particles to the ground over time. The full code is listed here: import org.lively3d.particles.*; //Import blowup class import org.lively3d.particles.ParticleBlowUp; //Declare particle array and particle var particles:Array = []; var particle:ParticleBlowUp; function explode(event:MouseEvent):void { var element:MovieClip = MovieClip(event.currentTarget); for(var i:uint = 0; i < element.numChildren; i++) { //Set random velocity and spin particle = new ParticleBlowUp(element.getChildAt(i)); particle.velocityX = Math.random() * 20-10; particle.velocityY = Math.random() * -20; particle.velocityZ = Math.random() * 20-10; particle.gravity = 1; particle.friction = .98; particle.spinX = Math.random() * 40-20; particle.spinY = Math.random() * 20-10; particle.spinZ = Math.random() * 20-10; particles.push(particle); } addEventListener(Event.ENTER_FRAME, myLoop); } function myLoop(event:Event):void { for(var i:uint =0; i < particles.length; i++) { particles[i].myUpdate(); } } //Add Listeners cards1_mc.addEventListener(MouseEvent.CLICK, explode); cards2_mc.addEventListener(MouseEvent.CLICK, explode); cards3_mc.addEventListener(MouseEvent.CLICK, explode); text_mc.addEventListener(MouseEvent.CLICK, explode);
236
Chapter 6: Working with Particle Systems In this section, you’ve learned a very important technique that you’ll use over and over in coming chapters. You’re not just passing variables to your methods, but movies and objects . . . and that opens up a whole new world of possibilities. You’re now going to use this new technique in another way – to interact with sliced images.
Slicing an Image Passing an object to a particle class can be used for more than just blowing things up. In this section, you slice up an image and turn it into particles. There are a number of reasons why you may want to slice up an image – to make a puzzle, game, or portal engine. But slicing images in a photo-editing tool such as Photoshop, Gimp, or Illustrator can get frustrating. Many developers don’t know that you can do this programmatically, which saves you time and a big headache if you have to re-slice when the project parameters change. Figure 6.7 shows a slice map of Walton County, Kentucky in the 1800s. The sliced map was created for a historic tour of the city, which has a rich history. The entire map rotates in 3D space around the x-axis and when you click on a section it elevates and rotates and then sinks back to its original position. In addition, there’s a drag-and-drop static method, which lets you drag the map pieces.
Figure 6-7 The slicing procedure consists of three important parts. The last two use the OOP principles discussed earlier: ❑
Slicing an Image
❑
Using Polymorphism & Encapsulation
❑
Using Static Properties/Methods & Interactivity
These key OOP concepts, previously discussed, are essential in understanding PV3D.
Slicing an Image Slicing an image is pretty easy using copyPixels. You just bring in your image and copy the portion of the image that you need to turn into a piece of the map.
237
Part II: Working with Models and Data // loop through all pieces for(var x:uint=0;x0x000000) { //Two different types of particles addSparkParticle(xpos, ypos, 1); addSmokeParticle(xpos, ypos, 3); //Jump ahead so you don’t make a million particles. ypos+=10; } } }}
A more interesting effect would be to take random pixels across the surface, generating particles whenever there’s an intersection with a desired color (see Figure 6.12).
247
Part II: Working with Models and Data
Figure 6-12 The code for scanning random positions is given here: function enterFrame(e:Event):void { updateParticles();
for(var i:Number = 0; i>16; if(col>0xee) { addSparkParticle(xpos, ypos, 1);
} } } function randRange(min:Number, max:Number) { var randomNum:Number = (Math.random() * (max-min )) + min; return randomNum; }
You may agree that particles are cool, but require much work to produce. And that’s where FLINT comes to the rescue.
248
Chapter 6: Working with Particle Systems
Flint Par ticles After all this work you’re probably asking if there’s a better way. And one avenue to go, if you want to spare yourself a bit of programming, is to use Flint. Flint is an open source particle system created by Richard Lord (of Big Room) and it works with PV3D. Flint can be downloaded from http://flintparticles .org/ and handles both 2D and 3D particles. The Flint site contains great tutorials and tons of examples with source code provided, so you cover only a few examples in this section, designed to get you started. In the book’s code are two viewers, which allow you to see 2D and 3D Flint examples.
Viewing Particle Examples The particle viewers are very similar to the MD2 viewer presented in the last chapter; therefore, the details aren’t presented here. Such viewers can come in handy, giving you the ability to quickly sort through a number of particle effects (see Figure 6.13).
Figure 6-13 Go to the book’s code and test out the (Flex) 2D and 3D FLINT viewers. With so many great tutorials available on using FLINT on the FLINT website, the basics won’t be covered here. Instead, you focus on using FLINT in PV3D.
Using Flint in Papervision3D PV3D’s particle system is a little insufficient, whereas FLINT has a more robust particle system. However, initially it may not be apparent how to use Flint with PV3D. FLINT actually has three renderers that can be used in conjunction with PV3D: ❑
PV3DPixelRenderer — renders each particle as a single pixel, is the fastest of the three, and
great for high particle density effects. ❑
PV3DParticleRenderer — renders particles according to a designated size and is ideal for less
dense particle systems, and you can billboard the particles. ❑
PV3DRenderer — renders a PV3D display object, which means that you can render any 3D object as a particle, but is the slowest of the three as a result.
249
Part II: Working with Models and Data At the time of this writing, both the PV3DParticleRenderer and PV3DRenderer have issues with some of the Flint color actions. For this example, you examine the PV3DPixelRenderer as it gives the most brilliant results of the three renderers, as shown in Figure 6.14. John Lindquist has posted a number of FLINT particle examples on his pv3d.org site and this discussion follows his work closely.
Figure 6-14
In the preceding example, you have two spheres orbiting in space and leaving behind a rocket trail. To get FLINT to do this in PV3D, follow these steps:
1. 2.
Import the appropriate FLINT and PV3D packages.
3. 4. 5.
Declare the Flint Pixel renderer (PV3DPixelRenderer).
Declare two FLINT 3D emitters (for your rocket trail) and two spheres (to lead the orbit) and add color materials to your spheres.
Initiate PV3D, the FLINT emitters, FLINT renderer, and createPixels method. In your animation loop, set the spheres in motion and attach the emitters to your spheres by setting their x and y values equal.
override protected function onRenderTick(event:Event = null):void { //Iterate Oscillation parameter myOsc++; //Execute Sphere Flight Path emitter1.position.x=sphere1.x=200*Math.cos(myOsc/50)*Math.sin(myOsc/100); emitter2.position.x=sphere2.x=200*Math.sin(myOsc/50)*Math.sin(myOsc/100); emitter2.position.y=sphere2.y=200*Math.cos(myOsc/50)*Math.sin(myOsc/100); emitter1.position.z=sphere1.z=200*Math.cos(myOsc/100); emitter2.position.z=sphere2.z=200*Math.sin(myOsc/100); //Rotate holder to create a nonlinear effect on flight path myHolder.rotationY+=.1; //Call the super.onRenderTick function super.onRenderTick(event); }
250
Chapter 6: Working with Particle Systems One trick that’s used here is that the spheres and emitters are placed in a display object and that object is rotated to give the paths and rocket trails a non-linear look. To see the entire code, consult the book’s chapter code.
Getting More Bang Out of Flash The big news is that Flash can now import C/C++ libraries using Alchemy, significantly ramping up its speed and functionality. Alchemy is a research project that allows users to compile C and C++ code that’s targeted to run on the open source ActionScript Virtual Machine (AVM2). With Alchemy, web application developers can reuse millions of lines of existing open source C and C++ code on the Flash Platform. Alchemy is primarily intended to be used with C/C++ libraries that have few operating system dependencies. Ideally suited for computation-intensive use cases, such as audio/video transcoding, data manipulation, XML parsing, cryptographic functions or physics simulation. Performance can be considerably faster than ActionScript 3.0, but it’s still a lot slower than native C++ because the AVM2 still doesn’t support multi-threading. Combining Pixel Bender and Alchemy allows you to render large particle sets, up to 300,000 – a number unheard of in the Flash community before Alchemy. It’s pretty amazing! Download and install Alchemy (from http://labs.adobe.com/technologies/alchemy/). Check out the book’s website for more on Alchemy.
Summar y This chapter gave a broad overview of particles in both PV3D and CS4. You started with the PV3D particle system and added some of your own particles to it and created a starry panorama. You built a 3D particle system from scratch and created a Flash CS4 glowworm. You learned how to slice and explode particle systems, and how to use them to interact with video. You took a look at the great work that Plug-in Media is doing and learned how to incorporate FLINT into PV3D.
251
Geocoding, XML, and Databases Bringing external data into your 3D application significantly enhances user experience, increases application value, and reduces development time. Showing your client a nifty 3D animation may not sell your product, but telling him that he can change it himself and won’t need your help after the product is completed will seal the deal. But chances are he’ll need you again, for a follow-up project. And you’ll be able to develop software more rapidly using external data sources as opposed to hard-coding your applications. PV3D wasn’t built to be data-driven; you’ve got to add the data piece in yourself. In this chapter you discover how to do this through a series of examples ranging from building an XML planetarium to a MySQL 3D Google Maps updater. Using the Flex data wizard, you find out how you can automatically generate PHP code, which you use to make server requests. Even though the emphasis in this chapter is on using PHP, the techniques that you learn can be easily extended to ASP.NET, Java, and ColdFusion. You start this fascinating journey with Geocoding.
Geocoding Pointing to a realistic place on a map and navigating to it on the web is called “Geocoding.” Geocoding has gained momentum in the open source community, which for some is a counterintuitive concept. The success of Google maps illustrates the point. After developing a robust interface, which parsed terabytes of satellite imagery and road data, they just gave it all away. This approach of “giving it away for free” has been met with great success, to the consternation of those who are scrambling to determine “value” on the web.
Part II: Working with Models and Data Google makes getting started with its Flash API really easy. On the API doc site there’s a stepby-step procedure for putting up your “hello world” map, tons of tutorials and helpful articles, and tons and tons of free examples with full source code. To get started visit their site at http://code.google .com/apis/maps/documentation/flash/.
Once there, you want to follow the procedure for getting your Map key and setting up your first Google Flash Map:
1. 2. 3. 4. 5.
Sign up for a Google Maps API Key. Download the Google Maps API for Flash SDK. Read the Developer ’s Guide. Read the API Reference. Join the announcements group to receive important updates.
Once you’ve put up your first map in 2D, you’re ready to go to 3D and PV3D. So let’s get started.
Putting a Google Map on a PV3D Prim Putting a Google map on a PV3D primitive is extremely easy. All you need to do is to turn the map into a Movie (using the MovieMaterial class) and then place it on a primitive. It’s a simple two-step process! And you can do it in only six lines of code:
1.
Turn Google Maps into a Movie:
movie.graphics.beginFill(0xFFFFFF); movie.graphics.drawRect(0,0,800,600) movie.addChild(Application.application.map); movie.graphics.endFill(); mat = new MovieMaterial(movie, false, true,true,new Rectangle(0,0,800,600));
2.
Place that movie onto a primitive:
sphere = new Sphere(mat, 600, 32, 32);
In this case, you use a sphere, but you can use any primitive you want. The results are shown in Figure 7.1.
254
Chapter 7: Geocoding, XML, and Databases
Figure 7-1 The real key is just converting the Map into a movie using the MovieMaterial class and placing it on a primitive. Everything else is just simple Papervision3D and the Google Flash API. You can run the API in either Flash or Flex. In the following set of examples, you use Flex, but the procedure is very similar for Flash and you should consult the Google Flash API docs for creating Flash apps.
Using Air (Flash Sandbox Security Issue) One of the first problems you run into when working with Google maps and PV3D is the Flash Sandbox issue. Whenever you use Google maps as a MovieMaterial (that is, BitmapData.draw) in PV3D you get a Flash security sandbox violation. When processing a MovieMaterial, PV3D uses the BitmapData.draw method in MovieMaterial.bitmapDraw. The Flash sandbox error occurs because the method won’t yield a result if the origin of BitmapData is not in the same domain. Basically Flash doesn’t want to grab this information from another server without permission. In your case, you don’t have access to the server that you’re loading the image data from, and therefore can’t create an appropriate cross-domain policy file (crossdomain.xml) on that server. The easiest solution to this problem is to use an Air application, which will only reside on your desktop and not on the web. But don’t fret; in the next section, you fix this problem using the Flash 10 player–. In Adobe Flex it’s easy to create an Adobe Air Application. Create a new Flex application and select Desktop application as shown in Figure 7.2.
255
Part II: Working with Models and Data
Figure 7-2
One big difference that you’ll notice immediately is that a Flex web application extends the Application class, whereas an Air application extends the WindowedApplication class. When building a Flex PV3D Google maps application, you have three important things that you must do to get your application to run correctly. ❑
You must create an Air application (as discussed above).
❑
You must obtain a Google Key based on an application URL and put both in your application:
key=”ABQIAAAA4LpFEu2In0mhf90kMq6mnRSIIINVTCMn36sCr8XLDIdy43daBStmo04li3ZOEGU0xaHRQBCkKZPxA” url=”http://www.professionalpapervision.com/demos/web/geocoding/”
You must place the Google Maps Flash API swc files in your Flex lib folder Failure to perform any of these three steps will cause your application to fail upon testing or deployment. The complete documented code for the application that you saw previously in Figure 7.2 is provided next. The code is very much like other applications presented in the book, so it isn’t covered here in detail. But if you’ve done your homework and worked through a few of the examples on the Google Flash docs website it’ll make total sense. Otherwise, check out the book’s video on the book’s website.
All right, this is well and good, but your client is screaming at you because he wants both a desktop application and a web application. To get this working on the web, you must use Flex 4 (or Gumbo).
Putting it on the Web with Flex 4 (Gumbo) The great thing about Flex 4 Gumbo (as with Flash CS4) is that it has a native z component and enables you to place 3D content on the web without the use of PV3D. You’re no longer restricted to using just an Air application as in the PV3D case above. Placing a Google Map on a CS4 Plane is easier in Flex 4 (Gumbo) than it is in PV3D. All you do is throw your map into a movie clip using the code below, and then center it to get your map pivot correct. The following code snippet sets your map in a movie clip, centers its pivot, sets map controls, places your movie in a Flex canvas, and centers your movie on the stage: //Place your map in a movie so you can position its pivot movie.addChild(map); //Set your map pivot map.x=-map.width/2; map.y=-map.height/2; //Add Map Controls map.addControl(new ZoomControl()); map.addControl(new PositionControl()); map.addControl(new MapTypeControl()); //Put your movie in a Flex Canvas mapHolder.rawChildren.addChild(movie); //Center your Movie movie.z=0; movie.x=450; movie.y=300;
This is the great power of CS4 over PV3D 2.0. You can now bring native Flex components into your 3D environment in conjunction with your 3D objects. Bringing Flex components into PV3D is a difficult task and requires the use of the draw BitmapData feature. But with CS4 it occurs natively, and those components can be manipulated in 3D as well.
259
Part II: Working with Models and Data The results are shown in Figure 7.3.
Figure 7-3 The code for the button components switches the label of the button upon transition to stop and start: //Pause or rotate button for your map private function pauseRotate(event:MouseEvent):void{ if(mouseBtn.label==”Stop”){ mouseBtn.label=”Start”; stopRotation = true; }else{ mouseBtn.label=”Stop”; stopRotation = false; } }
The code for your slider uses the change method to dynamically change your map’s rotation speed and direction: //Map Slider private function mySliderHere(event:SliderEvent):void{ rotationValue=-mySliderValue.value; }
The rest of the code is typical to what has already been demonstrated in this book or to Flash Google Maps and can be found on the API docs site. The full document code is given here:
This is just fantastic. You now have 3D access to an entire Google world (literally) of potential interaction. Use it to make a game, a worldwide presentation, or a virtual tour. The options are limitless. You now create a virtual tour using an XML backend.
XML Learning a bit of XML can really liven up your web application. XML is used in a vast majority of modern websites; it’s used for data-driven websites, RSS feeds, audio/video playlists, closed captioning, and configuration files, to name a few. But how to incorporate it into PV3D may seem a bit of a mystery. The good news is that E4X (ECMAScript) makes everything much easier, and it treats XML as characters, integers, and Booleans (as opposed to being accessed at the object level). This translates into an easy-touse dot syntax method, which lets you access XML nodes by name and throws out the difficult “childnode” language of past versions of Flash. A complete treatment of XML is beyond the scope of this text. For more info on XML consult the book’s website and blog. In addition, a great book on XML for both Flex and Flash is “XML and E4X” by Sas Jacobs. In the following section you jump right into XML.
262
Chapter 7: Geocoding, XML, and Databases
What Is XML Anyway? The first thing that you learn about XML is that it’s like HTML, but you can make your own tags. That may not make any sense to you, because it’s only half the story. After making your own tags, you must create a program that interprets (or parses) your tags. Parsing is like unwrapping a Christmas present which contains various items. Once the items are unwrapped, they’re identified and placed in their proper places (or your house will be a mess when your in-laws come over). Below, we give a typical XML file where client contact and project info is stored. The first line of code is the XML processing instruction, which includes your version as well as encoding info. The first node that’s placed in the file is the root node. In this case, the name of the root node is clients. Below that tag you have logInfo and fileNum and then the XML document contains two client nodes (or elements) with additional child nodes nested in them. Business Log 7 477751
[email protected] 123-45-6789 Spheres!]]> Tomorrow Spheres]]> Next Week
[email protected] 987-65-4321 Cubes!]]> Yesterday Cubes]]> Last Week
This XML document is referred to as being well formed, as each tag has a corresponding closing tag, and the order in which the closing tags appear is exactly the opposite of the order in which the opening tags appear within the file. For example, child elements must close before their parent elements, and nested elements close in the reverse order of their opening.
263
Part II: Working with Models and Data What may look a little strange are the CDATA tags found between the title tags. The CDATA tags just tell your XML not to process the info contained therein as XML. This gives you the ability to use formatting within these tags: such as to make text boldface. Flash only supports a limited number of HTML tags, but you also have robust CSS support. Having well-formed XML is very important. If your XML is not well formed, it won’t work in Flash or Flex. Each time you make an XML file you should validate its XML structure before trying to use it in Flash or Flex. This can save a lot of time. You can validate XML by opening the file in Dreamweaver and choosing File Validate As XML. If it validates, no error message is returned and your file is well formed. Otherwise, you get an error. When checking errors in XML, don’t just look at the error line, also look at the line (or lines) before the error, as error messages can cascade down and fool you. XML elements are known as nodes; even the information held between opening and closing tags is called a text node. You retrieve data from two places in your XML files – from text nodes and attributes. Attributes are like “name-value” pairs that appear inside of an opening element such as
where id is the label (or attribute tag name), and 01 is its value. Attributes can be used for more than just extracting data. They can also be used to search XML files (or as data filters).
Using E4X E4X stands for ECMAScript for XML. In AS3, Flash uses XML as a native data format, which means you can create XML in AS3 natively just the same way you work with any other data type; and in many cases it’s easier to do in AS3 than XML. For the XML clients example given above, to retrieve info in between element tags (or get a text node) using e4x, you just use dot syntax: dataXML.client[0].email
//Retrieves the text node
[email protected]
And to retrieve info from an attribute use the @ symbol as follows: dataXML.client[1].@id //Retrieves the value 02
Or to drill down deeper into your XML structure you could use the following dot syntax: dataXML.client[0].projects.project[1].endDate
264
//Retrieves Next Week
Chapter 7: Geocoding, XML, and Databases In all three cases, dataXML was assigned in Flex and represents your root document tag (clients). The generating code for the examples above can be found in the book’s Flash code in the ClientsLog folder. The great thing is that you’re retrieving XML data based on actual tag names. Another cool mechanism is the descendants operator (.) which lets you skip all descending tags and go directly to the tag name of interest. When you deal with more advanced XML structures, you’ll find the descendants operator extremely useful. As mentioned earlier, a full treatment of XML is beyond the scope of this book, and we assume that you’ve consulted the book’s website and blog for further info (or read Jacob’s book). You now proceed with the applications, and we point out the pertinent XML as it arises.
Taking a Historic Tour If you’re into historic preservation, you’ll find this section very useful. The project below is adapted from a prototype developed (by the author) for the City of Covington, Kentucky, and was used as a development tool for the city’s historic preservation activity. The prototype has the following features: ❑
3D rotating Map (x and z rotation sliders)
❑
Combo and Button and map type selection (Map, Satellite, Hybrid, Terrain)
❑
Pan and Zoom controls both Google and Custom Buttons
❑
XML driven map markers with text, audio, and image popup Flex custom component
❑
Audio engine that plays an audio description when clicked
❑
List Box with clickable navigation
❑
Toggle driving directions with navigation icon (penguin) and text directions
The application is shown in Figure 7.4, and can be obtained from the book’s chapter code.
Figure 7-4
265
Part II: Working with Models and Data At the heart of all these features is the XML file (named markersTour.xml) shown here: . . . (not all data is shown). . .
This particular XML file doesn’t use text nodes to hold data in between element tags; all of its data are attributes. Each marker contains eight attributes: name, address, lat (latitude), lng (longitude), type, audio, image, and descript (description). And they’re self-explanatory. But what may not be so selfexplanatory is how you get this data into Flex and then into your Google popup information window.
Importing XML into Flex and Flash Loading an external XML document can be accomplished by assessing a static XML document or by assessing one created from a database. Both Flex and Flash have several different ways of bringing in external XML data; both use the URLLoader class shown in the getXml() method below: public function getXml():void { var urlRequest:URLRequest = new URLRequest(“data/markersTour.xml”); var xmlLoader:URLLoader = new URLLoader(urlRequest); xmlLoader.addEventListener(“complete”, readXml); xmlLoader.load(urlRequest);//For this case, this step is optional }
In the getXml() method above, the URLRequest is instantiated as xmlString, which represents the URL of the file you’re going to load. It’s then loaded using the URLLoader, which is instantiated as xmlLoader. So in summary, the URLRequest represents the file you want to load and the URLLoader loads that file. Then an event listener listens for the URLLoader complete event and then calls the method readXML, which then parses the XML data. It’s important to note that you can use URLLoader to load any type of plain text file, not just XML. The final line makes the xmlLoader load the URLRequest, which is optional in this case, because you’re transferring the event data to the readXml() method.
266
Chapter 7: Geocoding, XML, and Databases Feeding XML Data into Your Popup The XML data is transferred to makersXML, an XML variable, which is used to create a markers XMLList. XML lists occur whenever there are repeating elements in a XML file. As shown in the code below, the markers XMLList variable is easily parsed using e4x. You should note the use of the modelLocator variables, which are used to transfer data to the Flex popup component. Model Locator is part of the Cairngorm architecture and is covered in the chapter on MVC. What’s important here is that you get the flow of how the XML is being parsed in a more advanced application. public function readXml(event:Event):void{ var markersXML:XML = new XML(event.target.data); var markers:XMLList = markersXML.marker; var markersCount:int = markers.length(); var i:Number; for (i=0; i < markersCount; i++) { var markerXml:XML = markers[i]; //Cairngorm values modelLocator.myName=markerXml.@name; modelLocator.myAddress=markerXml.@address; modelLocator.myAudio=markerXml.@audio; modelLocator.myDescript = markerXml.@descript; modelLocator.myImage = markerXml.@image; modelLocator.myType=markerXml.@type; modelLocator.myLat=markerXml.@lat; modelLocator.myLgn=markerXml.@lng; //Create the latlng variable var latlng:LatLng = new LatLng(markerXml.@lat, markerXml.@lng); acMarkers.addItem({label: markerXml.@name, marker: latlng, address: markerXml.@address}); //Create marker function var marker:Marker = createMarker(latlng, modelLocator.myName, markerXml.@address, markerXml.@type, modelLocator.myImage); map.addOverlay(marker); } }
Once the data is parsed using the @ symbol (as discussed earlier for attributes) it’s sent to the create marker method. When it comes to working with markers, a lot of people want to know how to bring in media to their markers such as images, audio, video, 3D models, and so on. And here’s the coolest trick that enables you to get the job done easily. The most significant trick you can use in Flex when working with markers, is to create a custom component for your marker popup. Using your custom popup component enables you to bring in any type of media that you want: audio, text, images, video, 3D models, and so on, as shown in Figure 7.5.
267
Part II: Working with Models and Data
Figure 7-5
What’s very handy is that the component has a remove from and add to stage method, which enables you to start or stop a process, such as turn off your audio when you hit the popup close button as shown in the method below: private function leaveStage():void{ if(soundChannel){ //Stop audio when the component leaves the stage and set the label. soundChannel.stop(); SoundBtn.label=”Play Audio”; }}
In addition, you can fill the popup with as much code as you like to accomplish your purpose (such as an audio player in the case above). The custom component acts like a class with a “visual view” for development purposes; speeding development, and keeping everything modular. The entire code can be downloaded from the book’s website. Next, you delve a little further into PV3D and CS4 and build a planetarium.
Building a Planetarium in PV3D Creating a planetarium, in principle, is easy.
1. 2. 3.
You grab star data from a site like NOMAD (http://www.nofs.navy.mil/nomad/). Put it in an XML file. Import that file into PV3D, and align your stars according to that data.
But you have two problems: PV3D’s Particle Material class doesn’t have a star image, and PV3D’s Particle Field class distributes particles randomly where you want to distribute them according to your XML star data (see Figure 7.6).
268
Chapter 7: Geocoding, XML, and Databases
Figure 7-6
Solution The solution to these two problems is to open up the classes in question and add the required functionality. Luckily, in Chapter 6 you did this for the ParticleMaterial class. So all you have to do is open up the Particle Field class and add the required functionality. In this case, it’s best just to make a copy of the file and rename it SkyMapArray. This keeps things working and gives you a reference to go back to in case you mess up the file with your modifications . . . and eventually you will! After you’ve renamed the file, change the name of the public class and constructor function to SkyMapArray.
In the constructor method, create a variable that imports the starArray, which holds your parsed star data. public function SkyMapArray(mat:ParticleMaterial, starArray:Array, particleSize: Number=4, skyRadius:Number = 2000)
With the starArray imported you can now create the method that distributes your data. The starArray is arranged as shown above which means that Abbreviation in the 0th position, Latitude in the 1st, Longitude in the 2nd, and Magnitude in the 3rd. starArray(Abbreviation, Latitude, Longitude, Magnitude)
The code is essential to know when positioning your stars as shown below. In the code, the array iterates over only a quarter of the values as everything is parsed in terms of four values. The myX, myY, and myZ coordinates are generated using the spherical coordinates equations discussed in the Chapter 3 on Prims. The size of your star is changed based on the star magnitude. This simulates distance. And finally the stars are thrown into the addParticle method, which places them on the stage.
269
Part II: Working with Models and Data //Iterate only over a quarter of the values var myPartNum:Number = starArray.length/4; for( var i:Number = 0; i < myPartNum; i++ ) { //Distribute your star based on Spherical Coordinates var myX:Number=skyRadius*(Math.cos(2*Math.PI*starArray[4*i+1]/24)*Math.sin((90starArray[4*i+2])*Math.PI/180)); var myZ:Number=-skyRadius*(Math.sin(2*Math.PI*starArray[4*i+1]/24)*Math.sin((90starArray[4*i+2])*Math.PI/180)); var myY:Number=skyRadius*Math.cos((90-starArray[4*i+2])*Math.PI/180) //Change size of your star based on brightness var mySize:Number=size*(3-starArray[(4*i+3)]); addParticle(new Particle(material as ParticleMaterial, mySize, myX, myY, myZ)); }
Finally, you need to bring in your XML using the URLLoader class already discussed and use the split command to parse your CSV (comma-separate values) data and place that data into a starArray. starArray=markersXML.myStars.split(“,”);
The XML file used for the star data, shown below, is just one huge CSV file. CSV stands for commaseparate values and is an industry standard text format for importing and exporting data between applications. AND, 2.065, 42.316, 0, AND, 1.161, 35.616, 1, AND, 0.655, 30.85, 1, AND, 0.139, 29.083, 1, . . .
Flash handles CSV data very easily by using a split command to separate the data, and then by placing that data into an array. Once in an array the data can be easily handled using an index. Once your starArray is created you just stuff it into the instance of your SkyMapArray and add it to your scene, and behold the heavens! stars = new SkyMapArray(particlemat,starArray,10, 5000); scene.addChild(stars);
So in principle it’s easy although there were a few things to do to get the job done. The entire documented code for the planetarium is given below: 6){camera.zoom-=.1;} } //rotate your camera based on mouse position camera.rotationY = camera.rotationY + (mouseX-stage.width / 2) / 500; camera.rotationX = camera.rotationX-(mouseY-stage.height / 2) / 500; if (camera.rotationX = 90) { camera.rotationX = 90; } } ]]>
272
Chapter 7: Geocoding, XML, and Databases
At this point, you’re probably wondering how difficult it might be to construct a planetarium in Flash CS4.
Building a Flash CS4 Planetarium Creating a planetarium in Flash CS4 is a natural extension of the previous discussion. As CS4 has a native z component, you can create your planetarium with significant code savings – using only 49 lines. In the previous PV3D example, you were inside of your star sphere looking out. To make things more interesting, in this example you’re on the outside looking in. This is of course a little nonphysical, as stars don’t sit on a sphere but extend out light years. However, the effect is pretty cool as you can see in Figure 7.7.
Figure 7-7 The figure demonstrates being on the outside looking in on your planetarium. Creating this application requires two main steps:
1.
Place a star object into your library (see Figure 7.8) so you can instantiate it onto the stage as you would your particles class. This step saves you tons of code:
var myStar = new Star();
Figure 7-8
273
Part II: Working with Models and Data 2.
Treat your stars like a particle system. This lets you stuff them into an updatable array so you can rotate them:
myParticles[j]
The rest of this stuff you’ve seen before. For a video explanation, you can check out the book’s website under this section. The full document code is given here: import flash.events.*; import flash.display.*; //Create star parameters var myStar:Star; var skyRadius:Number = 240; var myOsc:int=0; var starHolder:MovieClip= new MovieClip(); //Create a star array to hold CSV data and particle array var starArray:Array=new Array(); var myParticles = new Array(); //XML Statements used to bring in XML data var urlRequest:URLRequest = new URLRequest(“data/starData.xml”); var xmlLoader:URLLoader = new URLLoader(urlRequest); xmlLoader.addEventListener(“complete”, readXml); //XML parser and CSV splitter function readXml(event:Event):void{ var markersXML:XML = new XML(event.target.data); starArray=markersXML.myStars.split(“,”); //Set the initial star system in place and add to a Movie holder for(var i:uint = 0; i < starArray.length/4; i++){ //Instantiate stars and place them in a particle array var myStar = new Star(); myParticles.push(myStar); starHolder.addChild(myStar); //Position stars (x, y, z) on stage myStar.x=skyRadius*(Math.cos(2*Math.PI*starArray[4*i+1]/24)*Math.sin((90starArray[4*i+2])*Math.PI/180)); myStar.z=-skyRadius*(Math.sin(2*Math.PI*starArray[4*i+1]/24)*Math.sin((90starArray[4*i+2])*Math.PI/180)); myStar.y=skyRadius*Math.cos((90-starArray[4*i+2])*Math.PI/180); //Scale stars according to magnitude and give a rand rotation myStar.scaleX=myStar.scaleY=(1/9)*(3-starArray[(4*i+3)]); myStar.rotation=180*Math.random(); } //Add stars to star holder and position the holder addChild(starHolder); starHolder.x=400; starHolder.y=300;
274
Chapter 7: Geocoding, XML, and Databases //Create your animation loop using an Enter Frame Listener this.addEventListener(Event.ENTER_FRAME, onEnterFrame); } function onEnterFrame(event:Event):void{ //Increment oscillation parameter and iterate over stars myOsc++; for(var j:uint = 0; j < starArray.length/4; j++){ //Set new x, y, z for your stars as they rotate myParticles[j].x=skyRadius*(Math.cos(2*Math.PI*starArray[4*j+1]/24+myOsc/180) *Math.sin((90-starArray[4*j+2])*Math.PI/180)); myParticles[j].z=skyRadius*(Math.sin(2*Math.PI*starArray[4*j+1]/24+myOsc/180)*Math.sin((90starArray[4*j+2])*Math.PI/180)); myParticles[j].y=skyRadius*Math.cos((90-starArray[4*j+2])*Math.PI/180); }}
XML is great fun. It lets you store your data externally to your application. However, up to this point, you can’t change your data dynamically, and your users can’t permanently enter or change information. You need an intermediate language such as PHP, Coldfusion, or ASP to accomplish this. By using one of these intermediate languages, you can write your data dynamically to a flat-file, or MySQL database, for example Eventually, you’re going to find yourself saying (or thinking), oh CRUD!
Working with MySQL and PHP If you’ve been working with databases any amount of time you’ve probably heard the acronym CRUD, which stands for C-create, R-read, U-update, and D-delete. Essentially, every database performs these tasks: create records, read records, delete records, and update records. Using databases allows you to take your website to the next level. You’re able to store, search, delete, and change data dynamically. And large numbers of users will be able to interact dynamically with your data as well. As in the case for XML, you won’t get a blow-by-blow explanation of databases as there are many great books out there on how to use PHP and MySQL
What’s a Database Anyway? Don’t get too frightened when you hear the word database – it’s just a table with rows and columns like an Excel spreadsheet. And in the case of a MySQL database, there’s a simple language called SQL that allows you to place info in your table and edit that info. The thing that differentiates a MySQL database from a spreadsheet, however, is that the MySQL table has a lock and key, which means that in order to access it you must have the right key (username and password). And depending on who you are, you’re allowed certain privileges. It’s that easy, right? Not quite – there’s a catch. Your Flex application can’t talk (or transfer data) directly to your database. It needs an intermediate language, and in this case it’s PHP. You use PHP to turbo charge your Flex application, and if you’re good with AS3 already you’ll find PHP easy.
275
Part II: Working with Models and Data
Using Flex with a Database In Flex you can automatically generate the PHP code that you need to work with your MySQL database. This generated PHP code can be easily integrated into your Flex application and used to perform the CRUD operations described earlier.
Installing a Test Server Before you get started, you want to get a test server running on your local machine. This enables you easily to develop your applications locally and then upload them to the web when completed. The easiest way to install a test server is to use a server bundle, which contains (Apache, MySQL, and PHP). Three easy-to-install server packages are available for download depending on your type of machine: Linux, MAC, Windows. And the good news is that they’re all free! ❑
LAMP-Linux, Apache, MySQL, PHP
❑
MAMP-MAC (OS 10), Apache, MySQL, PHP
❑
WAMP-Windows, Apache, MySQL, PHP
For illustrative purposes, you step through the WAMP installation. The LAMP and MAMP installations have similar procedures, which can be found on their respective websites. To see a video on installing the MAMP server consult the book’s website. MAMP installation involves a little more work and requires setting ports. Follow these steps for installing WAMP:
1.
Download the WAMP Server from www.wampserver.com/en/ (this address may change, if so just search for WAMP server on the web).
2.
Make sure that you don’t have any other web servers listening on port 80 (the Internet information service port). The Apache server listens on port 80 and conflicts with any servers listening on that port.
3.
Now just click the WampServer.exe file and step through the information screens, accept the license, and install in the default folder c:\wamp.
4.
Click through a few more options and complete the installation. Once the installation is complete you’ll be able to start the server from your system tray menu that appears in the lower right hand position of the Windows screen.
5.
Once installed the WAMP installer looks for a web browser – go ahead and accept the default browser.
6.
Enter a SMTP server and email for using the PHP mail function (you can just click through this step if you’re not using this feature), click next and you’re finished.
Congratulations, you’ve done it! To start your WAMP server go to your start menu and choose WampServer start WampServer. When you start your WAMP server a gauge-like icon appears in your system tray that indicates when your server is ready to use. Clicking the gauge reveals the menu that’s shown in Figure 7.9.
276
Chapter 7: Geocoding, XML, and Databases
Figure 7-9 This menu gives you access to the administrative tools and lets you control the various services. For example, if you click “Put Online” button and select phpMyAdmin, the phpMyAdmin tool pops up. It’s a web interface that lets you manage your MySQL server – the greatest thing since sliced bread!
Creating a Map Maker Database In this section, you take the marker data found in the Covington Historic tour project, create a database, and load this data into that database using phpMyAdmin. First you want to open up phpMyAdmin as described in the previous section and create a database named MarkerSubset using the “Create new database” option. Then create a table, giving it the name SubsetData with seven fields (id, name, address, lat, lng, type, and descript), as shown in Figure 7.10.
Figure 7-10
The table is a subset of the Covington project data, because image and audio data are left out. Using image and audio assets requires an upload component, which we don’t cover in this chapter. To keep the storage requirements low, you can specify the lat and lng attribute. Set it to float of (10, 6). This allows four digits before the decimal and six after which is adequate for the zoom capabilities of
277
Part II: Working with Models and Data Google maps. Also, saving lat/lng values, as opposed to addresses for decoding to lat/lng values, reduces your processor load, making things run faster. If you don’t have your data in lat/lng format, use a batch converter. You can find them referenced on the Flash API Google Maps docs page. When you’ve created your database it’s time to generate the PHP required to talk to your database.
Automatically Generating PHP In this section, you create a Flex builder application that’s integrated automatically with the PHP. Here are the steps:
1.
Identify the root document folder for your application server (for Windows it’s www and for Mac it’s applications/mamp/htdocs). Your output folder goes here.
2.
Create a Flex project called MakerSubset, and from the Application server type drop down box choose PHP.
3.
Click next and provide the webroot folder C:\wamp\www (for windows) and /Applications/ MAMP/htdocs (for Mac).
4.
Then provide the RootURL: http//localhost and click the Validate Configuration button and you should get the message “The web root and folder URL are valid”. An error message could mean that you have another server running or you’re not on port 80.
5.
Click Finish. You’re now able to run both the client and server code of your Flex application on your local machine.
Now it’s time to start talking to your MarkerSubset database. To do so, you need to have a PHP service. Using the Flex data wizard you automatically create the PHP required to make the connection to your database.
Using the Flex Data Wizard The Flex data wizard introspects your MySQL database, and generates client and server side code required for CRUD operations. Here’s what you get when you run the data wizard on a database table: Client Side Code: Simple data entry Server Side Code: A complete CRUD interface for a particular table
Here’s the trick: use the data wizard to generate the code for each of the database tables in your database and then write your own custom client code and use the generated server code to talk to your database. Now you generate the PHP code:
278
1.
With your Flex MarkerSubset project active, go to the Flex menu and choose the Data menu item from the top of the page and choose Create Application from Database.
2. 3.
In the popup, select a data source.
4.
In the New connection profile popup, provide the database name (MarkerSubset) and set the username and password (for Windows leave the password blank and for Mac use root), and
Click New and provide the name MarkerSubset, which was created in the previous section, and click Next.
Chapter 7: Geocoding, XML, and Databases then click the Test Connection button to make sure that you’re connecting. If you did everything correctly you should get a message back: The connection was successful. If not, go back and make sure that you used the correct name for your database and that it’s running.
5.
Click Next, review the data, and click Finish. In the Choose data source window, you can choose the table you want to use for generating your PHP code. In this case, there’s only one table: SubsetData.
6.
Click Next. Your PHP source folder should be in the directory labeled bin-debug. The PHP file name should be the same as your table name with the first Character in caps. Then click Next and in the “Generate client-side code” popup you can select the columns you want to appear in the client side data grid, and in the drop down menu named Enable filtering on column, you can select which column you want to filter on. Only one column can be filtered on. You can change the code once it’s generated.
7.
Click Finish to generate the code. You get an editable data grid, which mirrors your database and lets you add and remove records.
Running the application produces the table that’s shown in Figure 7.11.
Figure 7-11
You can also insert records in your table by pressing the “folder-plus” icon that appears in the lower-left corner of Figure 7.ll. Pressing this icon produces the popup image that’s shown in Figure 7.12, which allows you to add a record to your marker database.
Figure 7-12
279
Part II: Working with Models and Data In addition to adding and deleting entire records, you can click on the individual items of the data grid and change them. Next, you take a look at the server code.
PHP Side The data wizard process generates quite a bit of code. The three most important generated files are: ❑
SubsetDataScript.as does the bulk of the ActionScript work and is included in the main
application through a script tag. ❑
SubsetDataconfig.as points to the location of your service PHP page (SubsetData.php).
❑
SubsetData.php contains the heart of your PHP code and executes the CRUD code shown
below. In the following code, the switch statement has five cases: FindAll, Insert, Update, Delete, and Count. Using the method request parameter, the switch statement selects one of these five cases and executes an internal method for that case and returns the required data. These five key words in the switch statement are important, as you use them to get marker data from your database using the method parameter: switch (@$_REQUEST[“method”]) { case “FindAll”: //Read $ret = findAll(); break; case “Insert”: //Create $ret = insert(); break; case “Update”: //Update $ret = update(); break; case “Delete”: //Delete $ret = delete(); break; case “Count”: $ret = rowCount(); break; }
In the next section, you learn to access your database from the client side. You create a custom client side application from scratch that sends a request to the PHP service, gets the data, and displays this data in your custom application.
Creating the Custom Client Side Application In this section, you examine the development of an application, which allows you to create, read, update, and delete marker data for a 3D spinning Google map. The program interface, shown in Figure 7.13, is actually a combination of the 3D spinning Google map that you created earlier in this chapter plus an updatable database component.
280
Chapter 7: Geocoding, XML, and Databases
Figure 7-13 Taking the Data Wizard results, you can now generate a custom client side application that makes a PHP service page request. You do this by using an HTTPService component:
When you execute the myService.send() method, it makes a request to the SubsetData PHP file that was created by the data wizard. The HTTPService instance calls the resultHandler method. The resultHandler method contains your action token, which determines which CRUD element that you’re going to execute. You initially set the action token to FindAll, which brings in all your database information, and creates your map markers. private function resultHandler(event:ResultEvent):void { //Create your action token which determines your CRUD action var token:AsyncToken = event.token; var markerObj:Object; //The switch case executes the CRUD required based on your action token switch (token.action) { case “FindAll”: //Read acMarker = event.result.response.data.row as ArrayCollection; //Create your map markers createMarkers(); break;
(continued)
281
Part II: Working with Models and Data (continued) case “Update”: //Update markerObj = event.result.response.data.row; acMarker.setItemAt(markerObj, markerGrid.selectedIndex); currentState=””; break; case “Insert”: //Insert markerObj = event.result.response.data.row; acMarker.addItem(markerObj); markerGrid.selectedItem = markerObj; markerGrid.scrollToIndex(markerGrid. selectedIndex); currentState = ““; break; case “Delete”: //Delete break; }}
acMarker.removeItemAt(markerGrid.selectedIndex);
What’s a Token? The AsyncToken class is a tracking mechanism. It tracks a request that’s sent to a service (using HTTPService when the response is received). In the custom application that you’re going to build, you make calls to many functions and you need a way to determine when a result event occurs, which operations are called, and the data associated with those operations. And that’s where the AsyncToken class comes into play. But where does it come from? The send method of the HTTPService returns an instance AsyncToken when you make a request to the server. The AsyncToken is a dynamic object and remains in application memory. When the result event occurs, the event object (ResultEvent) has a token property, which refers to the instance of the AsyncToken class that you created when you made the call. Inspecting the properties, from their names and values, determines when the event occurs, and the data associated with the executed operation. So you can create an action token as follows: var mapparams:Object = {method:”FindAll”}; var token:AsyncToken=myService.send(mapparams); token.action = “FindAll”;
In this code snippet the token is received from the send method, and in the next line you create an action property for your token. In this case it’s assigned FindAll, and when the result event comes back from the server, the event property points to this object. It can then be examined by your code and appropriate actions taken. . The action token is used in the resultHandler switch case to call the appropriate state and action. If you didn’t get all of this, check out the video tutorials on the book’s website or David Gassner on Lynda.com.
282
Chapter 7: Geocoding, XML, and Databases David Gassner of Lynda.com does a super job of explaining this process for ColdFusion, PHP, and ASP.net in his Flex 3 video training series Beyond the Basics. It’s important that you understand these concepts in order to build data-driven applications in Flex.
Creating Map Makers When the code executes, the action token is assigned the FindAll switch case variable. This is used in the resultHandler switch case and throws all the data in a data grid and then runs the createMarkers method. Most importantly, you can actually refer to the elements in your database by using the same E4X approach addressed earlier in this chapter. When the data is brought in using the FindAll action token, it’s transferred to an acMarker array collection as follows: acMarker = event.result.response.data.row as ArrayCollection;
This array collection can now be parsed just like any other E4X expression, by using the dot syntax notation to refer to the database table elements by name. Now isn’t that cool! This is demonstrated below as the for loop iterates over all the acMarker items in your library pulling out the lat, lng, and name data using dot syntax. //Create your markers public function createMarkers():void{ var i:Number; //Iterate over all the markers in your data base for (i=0; i < acMarker.length; i++) { var latlng:LatLng = new LatLng(acMarker[i].lat, acMarker[i].lng); map.addOverlay(createMarker(latlng,acMarker[i].name )); } }
The for loop the program calls a createMarker method, which refers to the marker data in your database using dot syntax and places your markers at the appropriate lat/lng and puts the marker name in a tool tip. If you want to add more markers, change, or delete them, you can easily do so using a custom marker form component.
Changing Data Finally, to change the data you use a custom component named MarkerForm, as shown in Figure 7.14. When you double click on a row of your application, or click on the New button, your application switches to a new state and this component drops down.
283
Part II: Working with Models and Data
Figure 7-14
When you’ve filled in the new data, and clicked Save, the information is dispatched through a MarkerEvent class, which holds all the data in a single currentMarker object.
Summar y In this chapter, you turned the corner from learning the inner workings of PV3D to using it to build datadriven web applications. You built a number of applications in Air, Flash CS4, and Flex, which illustrated the use of XML, PHP, and MySQL, and you learned how to use the Flex data wizard to automatically create PHP code, which was used to make server requests.
284
Gliding on AIR You’re about to take another step deeper into the world of modeling: terrain mapping. But you’re probably wondering, “Isn’t AIR a desktop application? Aren’t you trying to put things on the web instead?” And the answer to both questions is, “yes”. Using AIR you’ll create a modeling program that allows you to save your work into an XML file. The file can then be uploaded into Papervision3D and run on the web. It’s a simple equation: an AIR Modeling application outputs a data file that’s sucked up by PV3D and then used on the web. None of this is currently in PV3D, and in this chapter you learn everything you need to know to build your own modeling program. You start by examining how vertices are placed, height maps made, and terrains are built. You use AIR to save your XML outputted terrain maps, and import those maps into PV3D for use. You then extend this application and make saveable terrain maps from your webcam. Finally, you build a simple lathe.
Building a Simple Modeling Program One of the coolest things you can do in PV3D is to build a simple modeling program. As there’s so much difficulty in bringing models in from other programs (such as 3DMax, SketchUp, Blender), why not build your models in PV3D natively? Models built natively in PV3D function more efficiently. The geometry.vertices property is one of the keys that aid you in creating modeling programs in PV3D. This property let’s you rearrange vertices programmatically, and place particles in various geometric arrangements. You start by investigating the use of this property by creating the infamous Rubik’s Cube.
Part II: Working with Models and Data
Placing Particles at Vertices You may not think of a Rubik’s cube as a particle system, but it is. You can think of it as 26 particles that are placed at various associative points in space. Placing your cubes at these points may seem like a mathematical mess, but there’s a trick that requires only eight lines of code. The end result is shown in Figure 8.1.
Figure 8-1 To lay out your Rubik’s cube, you use the geometry.vertices property to anchor your particles (which are cubes in this case) to the vertices of a larger cube, as shown in Figure 8.2.
Figure 8-2 You can think of the process in terms of one large cube with many small cubes.
Large Cube Segmenting a large cube by 2, 2, 2 creates the coordinates of the Rubik’s cube as shown here: cubeGrid = new Cube(myMaterialsList1, mySize, mySize, mySize,2,2,2);
As previously stated, you place small cubes at each vertex of the large cube using the geometry. vertices property.
286
Chapter 8: Gliding on AIR Small Cubes The geometry.vertices property gives you the position of each large cube vertex, which is stored in the vertices array and accessed using the geometry.vertices method as shown below: for(var i:int = 0; i
Incorporating Adobe AIR Air is definitely the tool of choice when it comes to building an editor. Eventually you want to save the editor output somewhere: in a web file, to a database, or to a file on your local PC. One of the problems with Flash (before Flash player 10), was that you couldn’t access the user ’s local file system and it was difficult to save to the local hard drive (or desktop).With Flash 10 you can now save to the desktop, so why use AIR?
306
Chapter 8: Gliding on AIR Air also provides a SQLite database, a higher level of security, a desktop application for distribution, and a number of other great features that we don’t cover here. As a number of great books are available out there by WROX on AIR, we won’t be covering AIR in detail here. However, you’ll learn what you need to get your application to save to your local PC or into a local SQLite database.
Accessing your Local File System Using AIR you can create, read, and write files and directories to your local hard drive. When working with the local file system, you need to determine if you’re going to work synchronously or asynchronously. Here’s what that means: ❑
Synchronous means that operations, while reading or writing data, are suspended during the process. So if you’re running animation or user interactions, the front-end thread is brought to a halt while Air does its thing (or completes its file operation).
❑
Asynchronous means that the front-end application thread still runs and your file operations are run in the background. If you have a large amount of data to work with, this is the way to go, or your application will become temporarily unavailable.
Coding for synchronous and asynchronous file operations is somewhat similar in both cases. Implementing asynchronous file operations requires that you listen for the complete event and react when the file operation is finished. The two key classes that you’ll be working with are the File and FileStream classes. The File class represents a file or directory on your local PC. It supports moving, deleting, copying, browsing, and everything you want to do with a file or directory. The FileStream class reads and writes data to a local drive. You now use these classes to save your editor ’s heightmaps to your local PC.
Saving BitmapData Most flashmaticians will agree that the BitmapData class is one of the most useful classes ever created in Flash. In this section, you use it to save your grayscale heightmap. Saving your heightmap is a simple process as Flex already has an image encoder built into its framework. This is the process: Modify your bitmap using the heightmap application built in the previous section, then encode it using the appropriate JPEG or PNG encoder (which is part of the mx.graphics.codec package). Then save the encoded file using the File and FileStream classes discussed in the previous section. The good news is that your bitmap is ready to go. It’s the canvas you’ve been drawing on (named myBitmapData) and all you have to do is send it to your jpeg or png encoders. //JPEG encoder var jpgenc:JPEGEncoder= new JPEGEncoder(80); imageByteArray=jpgenc.encode(myBitmapData); //PNG encoder var pngenc:PNGEncoder= new PNGEncoder(); imageByteArray=pngenc.encode(myBitmapData);
Then you use your file stream class to write your encoded data to the correct file location.
307
Part II: Working with Models and Data //Use a FileStream to save the bytearray as bytes to the new file var fStream:FileStream = new FileStream(); //open file in write mode fStream.open(myFile,FileMode.WRITE); //write bytes from the byte array fStream.writeBytes(imageByteArray);
The File class is used to create files and directories and resolve paths. //resolvePath gets a file object that points to the correct file var myFile:File = File.desktopDirectory.resolvePath(imageFolder+fileName); //Determine if the file exists if(myFile.exists){ //File doesn’t exist, create it return getNewImageFile(filext); } return myFile; }
With this code you also need a simple user interface for saving your files and choosing your file type (.jpeg or .png) as shown in Figure 8.11.
Figure 8-11
All you do is type in the name you want to save your image as in the textbox, select the type of image (.jpeg or .png) and click the Save button. When the save process is complete you get an Alert box indicating where the image file was saved to (see Figure 8.12). The location of the save file is resolved using the method: myFile.nativePath. Alert.show(“Image is saved at “+myFile.nativePath,”Image Saved”);
Figure 8-12
308
Chapter 8: Gliding on AIR So basically, after adding all the necessary import statements and properties, you just drop the code below into the heightmap editor program written in the previous section. //Save And Load Files private function saveMyImage():void{ var formatSwitch:int; if(myJpg.selected) formatSwitch=0; if(myPng.selected) formatSwitch=1; //Choose encoding format jpeg or png switch(formatSwitch){ case 0: filext=”.jpg”; //JPEG encoder var jpgenc:JPEGEncoder= new JPEGEncoder(80); imageByteArray=jpgenc.encode(myBitmapData); break; case 1: filext=”.png”; //PNG encoder var pngenc:PNGEncoder= new PNGEncoder(); imageByteArray=pngenc.encode(myBitmapData); break;
} //Gets a reference to a new empty image file var myFile:File = getNewImageFile(filext); //Use a FileStream to save the bytearray as bytes to the new file var fStream:FileStream = new FileStream(); try{ //open file in write mode fStream.open(myFile,FileMode.WRITE); //write bytes from the byte array fStream.writeBytes(imageByteArray); Alert.show(“Image is saved at “+myFile.nativePath,”Image Saved”); //close the file fStream.close(); }catch(e:Error){ Alert.show(“Image is not saved!”,”Saving Error”); trace(e.message); }} //Returns a unique new image file reference + specified extension private function getNewImageFile(filext:String):File{ //Create a new unique filename using time stamp var fileName:String = myImageName.text+getMyTimestamp()+filext;
(continued)
309
Part II: Working with Models and Data (continued) //Create a reference to a new file in the desktop folder //Any directory that doesn’t exist in the path is created //Resolvepath gets a file object that points to the correct file var myFile:File = File.desktopDirectory.resolvePath(imageFolder+fileName); //Determine that the file exists if(myFile.exists){ //File doesn’t exist, create one return getNewImageFile(filext); } return myFile; } //Returns a timestamp private function getMyTimestamp():String{ var date:Date = new Date(); var timeStamp:String = date.getFullYear().toString()+date.getMonth()+date.getDate()+date.getHours()+ date.getMinutes()+date.getSeconds(); return timeStamp; } //Method for the save button private function onClickSave():void{ saveMyImage(); }
As mentioned earlier, Flash 10 gives you the ability to save to your local PC. You now cover how to accomplish that.
Saving to Your Local PC Using Flash 10 Saving to the local PC is something that the Flash community has wanted to do for years – and it’s finally here! Hurray! Saving uses the FileReference class; all you have to do is add two lines of code. var saveFileRef:FileReference = new FileReference(); saveFileRef.save(imageByteArray);
After adding this code to your editor program and deleting all the unneeded code, just run the program, make a terrain map, and hit Save. You then get a dialog box asking you where to save the file as shown in Figure 8.13. In this particular application of the FileReference class, you need to add the file extension to your file name even though you selected the encoding method from the radial buttons.
310
Chapter 8: Gliding on AIR
Figure 8-13 That’s all there is to it! Next, you’re going to turn a webcam image into a heightmap.
Turning Your Webcam into a PV3D Heightmap Flash has always been advertised as a tool that’s only limited by your imagination. Right now that statement has never been truer. As advanced features get added with each new Flash player, the possibilities for doing super-cool creative stuff grow exponentially. Flash now crosses all media boundaries and as a demonstration of the fun and power of Flash, you’ll turn your webcam into a heightmap. This is much easier than you may think. You’ve already written most of the code in the previous sections, and with the addition of one line of code you can grab your webcam for a heightmap and then edit it. myBitmapData.draw(camVideo);
This code draws your webcam image to your bitmap. The results are shown in Figure 8.14 and look like Han Solo frozen in carbonite. It’s one of those cool but useless effects that have the potential for leading to something useful.
Figure 8-14
311
Part II: Working with Models and Data Even though your webcam image is in color, the terrain editor converts it to grayscale using the getHeight method discussed previously. The method averages your red, green, and blue colors to produce a grayscale height. There’s one more important iteration – it’s integrating your editor with your viewer so that you can see a live preview of your work. All you have to do is combine the two programs, so that the bitmap being fed into your terrain viewer is coming from your grayscale editor. For now we’ll leave this as an exercise, but we address it again.
Using SQLite Saving data to a database (before AIR) usually meant talking to an external server using a language like PHP, ColdFusion, ASP.net. But AIR comes with an embedded SQLite database engine. AIR databases are stored on your local system with a .db extension, and must be stored in areas where AIR has the proper permission to read and write data. Here are the steps to using a SQLite database. ❑
Use the File class to point to the location of your database.
❑
Use the SQLConnection class to connect your database.
❑
Use the SQLStatement class to define your SQL statements.
❑
Connect your data. Similar to accessing file systems, your connection can also be synchronous or asynchronous.
When you’ve connected your database, you communicate with it using a slimmed down version of SQL. Essentially this amounts to support for standard CRUD operations, as mentioned in the previous chapter: Create, Read, Update, and Delete. One of the cool things you can do with AIR is create an “occasionally” connected application; grabbing data from a server data base and storing it locally, then disconnecting for a while, and then reconnecting to synchronize data again.
Deploying AIR After you’ve built your Air application, you’ll want to distribute it using an installer file. Two types of installer files exist for distribution: a digital certificate, or self-signed. The problem with self-signed is that the Air app reports your identity as unknown when using the installer, which looks unprofessional and often causes the user to question the validity of the application being installed. Because Air can access your local machine files (unrestricted access), an unknown installation has the potential to do great damage to your machine, data, and even compromise your personal security by stealing information associated with your identity. In other words, the danger is real, so being marked as “Unknown” without a certificate isn’t unwarranted. You can get a certificate from Thawte or VeriSign. Of course, the downside is that these certificates aren’t free. However, if you’re distributing an AIR application on the web, you should probably consider getting one. Creating installer files is covered on the book’s website and blog. There is one last topic that you want to consider before concluding, and that’s sculpting.
312
Chapter 8: Gliding on AIR
Sculpting a Prim Using face3d In order to build a modeling program in PV3D such as 3DSMax (or PaperMax in this case), you need to be able to sculpt prims. You can sculpt prims using the face3d method. This method was created by John Grden, part of the PV3D core team, and gives you the ability to transform triangle vertices when you roll over a triangle face. Figure 8.15 shows an example of a simple lathe program created using simple key listeners, InteractiveScene3DEvent listener, and the face3d method.
Figure 8-15
Here’s how it works. The lathe object (a sphere in this case) continually spins. When the w-key is pressed the active triangle faces (those being rolled over) are pushed out from the center and when the s-key is pressed the active triangle faces are pushed in towards the center. The main method that accomplishes this is the sphereMorphOver method that’s shown here: //Lathe Method private function sphereMorphOver(event:InteractiveScene3DEvent):void { //Check your Boolean values if(booleanGoOut||booleanGoIn){ //Grab the three vertices of a triangle var vertexv0:Vertex3D = event.face3d.v0 as Vertex3D; var vertexv1:Vertex3D = event.face3d.v1 as Vertex3D; var vertexv2:Vertex3D = event.face3d.v2 as Vertex3D; //X and Z coordinates if(booleanGoOut){ //Push triangles out myZ=-5*Math.cos(sphere.rotationY*Math.PI/180);
(continued)
313
Part II: Working with Models and Data (continued) myX= 5*Math.sin(sphere.rotationY*Math.PI/180); }else{ //Push triangles in myZ= 5*Math.cos(sphere.rotationY*Math.PI/180); myX=-5*Math.sin(sphere.rotationY*Math.PI/180); } //Translate triangle faces (in or out) vertexv0.z+=myZ; vertexv1.z+=myZ; vertexv2.z+=myZ; vertexv0.x+=myX; vertexv1.x+=myX; vertexv2.x+=myX; } }
The method checks your Boolean values (set by your key action) and then sets the value of myZ and myX (based on prim rotation) which determines which direction your triangle faces are pushed (in or out). Finally, your triangle vertices (obtained from the face3d method) are adjusted based upon these myZ and myX values. The complete documented code is given here: package { //Flash Imports import flash.events.Event; import flash.events.KeyboardEvent; //Papervision3D Imports import org.papervision3d.core.geom.renderables.Vertex3D; import org.papervision3d.core.proto.MaterialObject3D; import org.papervision3d.events.InteractiveScene3DEvent; import org.papervision3d.materials.WireframeMaterial; import org.papervision3d.objects.primitives.Sphere; import org.papervision3d.view.BasicView; //Set Stage Meta data [SWF(width=”640”, height=”480”, backgroundColor=”#222222”, frameRate=”24”)] public class SimpleLathe extends BasicView { //Declare Lathe properties private var sphere:Sphere; private var booleanGoOut:Boolean=false; private var booleanGoIn:Boolean=false; private var myZ:Number; private var myX:Number; public function SimpleLathe() { //Make Viewport interactive super(640, 480,false, true); //Instantiate an Interactive Sphere
314
Chapter 8: Gliding on AIR var material:MaterialObject3D = new WireframeMaterial(0x00ff00); material.interactive = true; sphere = new Sphere(material, 300, 30, 30); //Over Sphere Method sphere.addEventListener(InteractiveScene3DEvent.OBJECT_MOVE, sphereMorphOver); //Set Keyboard Listeners stage.addEventListener( KeyboardEvent.KEY_DOWN, keyDownHandler ); stage.addEventListener( KeyboardEvent.KEY_UP, keyUpHandler ); //Add Sphere to your stage, set rotation, and start spinning scene.addChild(sphere); sphere.rotationY=0; startRendering(); } //Lathe Method private function sphereMorphOver(event:InteractiveScene3DEvent):void { //Check your Boolean values if(booleanGoOut||booleanGoIn){ //Grab the three vertices of a triangle var vertexv0:Vertex3D = event.face3d.v0 as Vertex3D; var vertexv1:Vertex3D = event.face3d.v1 as Vertex3D; var vertexv2:Vertex3D = event.face3d.v2 as Vertex3D; //X and Z coordinates if(booleanGoOut){ //Push triangles out myZ=-5*Math.cos(sphere.rotationY*Math.PI/180); myX= 5*Math.sin(sphere.rotationY*Math.PI/180); }else{ //Push triangles in myZ= 5*Math.cos(sphere.rotationY*Math.PI/180); myX=-5*Math.sin(sphere.rotationY*Math.PI/180); } //Translate triangle faces (in or out) vertexv0.z+=myZ; vertexv1.z+=myZ; vertexv2.z+=myZ; vertexv0.x+=myX; vertexv1.x+=myX; vertexv2.x+=myX; } }
//-----------------------------------// Key Listeners //------------------------------------
(continued)
315
Part II: Working with Models and Data (continued) //Set Boolean Values //Key press method private function keyDownHandler( event:KeyboardEvent ):void { switch( event.keyCode ) { case “W”.charCodeAt(): booleanGoOut = true;//Start push out break; case “S”.charCodeAt(): booleanGoIn = true;//Start push in break;
} } //Key up method private function keyUpHandler( event:KeyboardEvent ):void { switch( event.keyCode ) { case “W”.charCodeAt(): booleanGoOut = false;//Stop push out break; case “S”.charCodeAt(): booleanGoIn = false;//Stop push in break; } } //Animation Loop override protected function onRenderTick(event:Event = null):void { //Continually Spin Sphere for Lathe sphere.rotationY+=1; renderer.renderScene(scene, camera, viewport);
} } }
As soon as you’ve sculpted the prim you need to save it using the techniques discussed previously. This program shows how easy it is to sculpt prims inside of PV3D. Using techniques like this you can create an entire modeling application, which would alleviate many of the problems with parsing externally generated models. But obviously there’s still much work to be done on this application to bring it up to that level. To see more on this topic check out the book’s website.
316
Chapter 8: Gliding on AIR
Summar y In this chapter, you built your first editor in Adobe AIR. During the development process you mastered a number of uses for basic Flex components, which you’ll use again in other applications. Additionally, by accessing your local PC file system using AIR and Flash10, you saved the results from the editor you built to your local hard drive. You also learned about creating grayscale heightmaps and using the geometry.vertices property to bring those maps into PV3D. Using this, you then created a PV3D terrain viewer to view your heightmaps. Extending your editor, you captured your webcam and programmatically changed it into grayscale, and you brought it into your PV3D terrain viewer. Finally, you built a simple lathe using the face3d method created by John Grden.
317
Par t III: Building Games and Websites Chapter 9: Incorporating 3D Physics Chapter 10: Building 3D Games for Wii Chapter 11: Integrating the Flash Media Server Chapter 12: Developing 3D Websites
Part III: Building Games and Websites In Part III, you explore how you bring physics into PV3D by creating custom physics classes. You modify the DisplayObject3D class, and you learn to use Jiglib. You build Wii controlled games, investigate creating multiplayer games using the Flash Media Server, and create a 3D website.
320
Incorporating 3D Physics Physics is not yet well developed in PV3D, but having a good physics engine is a must for building dynamic websites and games. Good physics engages users in kinesthetic modality, activating the “buying” part of their brains. Like particles, physics provides an important element of realism to your application. When a camera follows a racing car taking a sharp turn, the camera veers off a little to demonstrate centripetal force. This requires physics. In this chapter we demonstrate the method of adding physics to PV3D. Also in this chapter you work through a number of examples, illustrating different approaches for incorporating physics into PV3D. You learn to make your objects interactive using the Interactive Scene Manager, and how to build large-scale applications using states, modules, and porting. You also add acceleration and gravity to your camera, as well as orbiting particles. You discover how to build encapsulated oscillation classes as well. Finally, you discuss 3rd party physics engines: WOW and Jiglibflash, and then build some Jiglibflash starter codes to be used in the next chapter. This chapter is packed with examples, not all of which could be included in full in the text. In some cases, just highlights with code snippets are given, but for all examples, you can obtain the full code from the book’s website.
Understanding the Guts of Physics Believe it or not, the entirety of physics consists of only four concepts: position, time, mass, and perspective.
Part III: Building Games and Websites ❑
From position and time you get: x, y, z, velocity, acceleration, and jerk.
❑
Adding mass to the mix produces: momentum, force, energy, and work.
❑
Adding perspective yields: frames of reference, macrophysics and microphysics, and the un-unified laws of physics (too numerous to name).
If you think this is an over simplification, think again . . . it took mankind thousands of years to figure it out. Real physics started with Isaac Newton and became elegantly and necessarily confused by Albert Einstein and Paul Dirac. Physicists are now trying to tie it all together with strings (String Theory), which really means that they’re starting back at square one, trying to understand the universe again. The core dilemma stems from perspective. If your frame of reference is distant from something, you apply Newtonian Mechanics; if it is really close, then you apply Quantum Mechanics; and if you’re going really fast, you apply Relativistic Mechanics (“Special relativity”). Nothing is unified – you just change the laws of your environment based on your frame of reference (or where your camera is placed) and how fast you’re going. In this chapter, you stick to Newtonian physics.
Adding Physics to Your Camera PV3D gives you the ability to use multiple cameras and add physics to those cameras as well. The key to killer gaming is providing a realistic camera experience for your user. For example, in a racing game you don’t want your camera to follow your vehicle’s position exactly; this produces an unrealistic feel to your game. You need to add a little springiness! One of the best improvements ever made to PV3D’s camera set was the Spring Powered Camera, created by Benoit Beausejour (see http://agit8.turbulent.ca/bwp/). Using spring physics, your camera is attached to a moving target using the PV3D SpringCamera3D class (found in org/papervision3d/ cameras). Here’s how it works: Your camera is linked by a spring to your target like a car or a boat. When your target moves in 3D space, the spring extends and then pulls the camera along. This gives it a natural movement as opposed to a stiff following action. When your speed-racer makes a sharp turn your camera sways out a little. This is exactly what you want. But how is it coded? Benoit added a number of physical parameters to the camera: mass, spring stiffness, damping, velocity, acceleration, and force. His equations follow from Hooke’s damping equation (and linear iterative approximations). Benoit gets a viscous or damping force by multiplying a camera velocity by damping. He gets spring force restoration (stretch) by multiplying the change in position by stiffness (Hooke’s law). Then he calculates the total force on the camera by subtracting the damping force from the restoration force. He calculates acceleration by dividing force by mass, and then steps it down to camera position. This sounds like a lot, but it really isn’t that much code: //Adding Stiffness to get Hooke’s restoration force _stretch.x = (x - _desiredPosition.x) * -stiffness; _stretch.y = (y - _desiredPosition.y) * -stiffness; _stretch.z = (z - _desiredPosition.z) * -stiffness;
322
Chapter 9: Incorporating 3D Physics //Multiply velocity by damping to get damping force _dv.x = _velocity.x * damping; _dv.y = _velocity.y * damping; _dv.z = _velocity.z * damping; //Subtract damping force from restoration force to get the force on your camera _force.x = _stretch.x - _dv.x; _force.y = _stretch.y - _dv.y; _force.z = _stretch.z - _dv.z; //Divide force by mass to get acceleration _acceleration.x = _force.x * (1 / mass); _acceleration.y = _force.y * (1 / mass); _acceleration.z = _force.z * (1 / mass); //Calculate velocity _velocity.plusEq(_acceleration); //Step velocity down to position of your camera _xPosition.x = x + _velocity.x; _xPosition.y = y + _velocity.y; _xPosition.z = z + _velocity.z;
As you ponder the best way to add physics to PV3D, Benoit’s approach merits some consideration. As opposed to relying on a 3rd party engine, he incorporated the physics directly into PV3D. This obviously is the most efficient approach since you don’t double process your calculations to get your PV3D and 3rd party physics engine running at the same time. However, the physics engine in PV3D is not well developed and using a 3rd party engine really saves time (if you can tolerate the double processor hit). Using Benoit’s spring camera you can also create different camera perspectives.
Creating Camera Perspectives In creating any game you need to determine your camera perspective early; changing game perspective mid-way can deal a death blow to your game’s development – scrapping months of work or causing you to eat precious hours already spent on a project. There are three perspectives that you need to consider in game development: ❑
1st Person — a vantage point that attempts to simulate looking through a game character ’s eyes. It is most commonly found in First-Person Shooters and Racing games. This is good for close-action.
❑
3rd Person — seen at a distance from a number of different possible perspective angles. Your point of view is looking at the character you’re playing. This is good for seeing the global perspective in a story.
❑
2nd Person — can be one of two things: a combination of both First and Third persons where you see a portion of your character, but not all, or your character is seen through the eyes of another player. In both cases, some portion of the character is shown. This creates a more complex psychology.
323
Part III: Building Games and Websites When deciding which perspective to use, you should ask yourself the following questions: ❑
Are you inside or outside? If you’re inside a tight hallway or a jet cockpit use 1st person. Otherwise, your camera gets hung up on the walls or equipment.
❑
What do you want your player to see? If you’re blowing everything up and need to see all the action then use 3rd person. 3rd person will allow you to see the complete explosive effect, and let you remain oriented to your environment as the action happens.
❑
How do you want to tell your story? If the psychology of the game is complex (as opposed to blowing everything up) then 2nd person is the way to go. All the rules for making a good film and how you use your camera apply here!
The bottom line is that games are for exploring – your character, other characters, equipment, environment, your skills, and so on . . . check out Reid Kimball’s blog article on GAMASUTRA for more info on this topic at http://www.gamasutra.com/. Choose the camera perspective that most enhances your ability to explore. And you aren’t limited to just one camera. If you jump into a jet cockpit, change your perspective to 1st person. Then change it back to 3rd person when you eject from your jet and run across the desert to fight the enemy. And when you’re being interrogated go to 2nd person as the evil villain slashes away at you. Using a spring camera and a switch case makes it easy to do, as shown in the following code: switch (cameraSwitch) { //Switch to First person case 0: renderer.renderScene(scene, break ; //Switch to Second person case 1: renderer.renderScene(scene, break ; //Switch to Third person case 2: renderer.renderScene(scene, break ; //Switch to Top view case 3: renderer.renderScene(scene, break ; default: trace (“Not in my list!”) ; }
firstPersonCam, viewport);
secondPersonCam, viewport);
thirdPersonCam, viewport);
topViewCam, viewport);
Using the switch case above lets you add as many cameras as you want to your 3D scene. We demonstrate this approach in the next section, where you build a star cruiser.
Cruising the Stars Using Cameras Using the Spring Camera class and camera switching you can build a dynamic navigation system using control points. Figure 9.1 shows an elongated-flattened cylinder, which cruises a double row of galaxy lights. The lights act as navigational control points that navigate through an XML-generated star system.
324
Chapter 9: Incorporating 3D Physics
Figure 9-1 Here are the key points of the program: ❑
The navigation markers are actually spheres brought down to 2x2 segments, which makes them look like diamonds. They’re distributed through an XML generated star system, and the navigation spheres are placed in this star system using a parametric equation (in this case a 3D figure eight).
❑
The two marker sets (top and bottom) are placed in two separate display containers, which are then added to your scene.
❑
The speed of the cruiser can be adjusted using up or down keyboard arrows. And the cameras can be switched from five different states:
❑
❏
1. 1st person
❏
2. top close view
❏
3. 3rd person
❏
4. top far view
❏
5. 2nd person, using the left or right keyboard arrows
The ship follows the marker navigation trail by iterating to the next path position pointNav generated by your navPath method, as shown in the following code:
//Increase Nav Object Speed myObject.extra.angle += mySpeed ; //Place the nav cylinder on the next position around the navPath var point:Number3D = navPath(myObject.extra.angle); var pointNav:Number3D = navPath(myObject.extra.angle+(0.4));
Using the extra object, included in the DisplayObject3D class, is essential to the navigation routine. The extra object is a public property of the DisplayObject3D class that lets you store data about your
325
Part III: Building Games and Websites object’s state. So in the code snippet above the position of your object is taken from the marker ’s navigation path method navPath (stored in the extra object) and then projected to an iteration of .4, giving the next point of navigation. Your craft is then made to move to this new position, giving it motion along the marker path created by the method navPath. An important property of display objects is extra, and the code snippet that creates it in the DisplayObject3D class is given below: //All properties of the extra field are copied into the instance. The properties specified with extra are publicly available. public var extra :Object;
Most click interactions use the “extra” property to determine what to do when an object’s state changes. For example, you could create an energy extra value for a particle, and over time, as your particle’s energy depletes, have your particle return home. An if statement is used in your animation loop and listens to your particle’s energy state and sends it home if its energy is lower than a certain value. Don’t be afraid to add additional parameters to your DisplayObject3D class as needed. You won’t break anything. The entire code for the star cruiser is given in the Chapter 9 book code. No doubt, the most important class in PV3D is the DisplayObject3D class. In this next section, you find out how to modify this class and add a little physics.
Adding Physics to the DisplayObject3D Class You can easily add physics to any PV3D object by modifying the DisplayObject3D class and adding the desired physical parameters, such as mass, velocity, and acceleration. As an example of this, you’ll add gravity to the DisplayObject3D class. Adding gravity to a sphere allows you to create orbiting planets. But planets have mass, so the first order of business is to add mass to the PV3D DisplayObject3D class. So open up the DisplayObject3D class and first add the mass property. private var _mass:Number
Next, add the mass getter and setter methods so you can get and set the mass property. public function get mass():Number { return this._mass; } public function set mass( value:Number ):void { this._mass = value; }
Now that you’ve added mass to your DisplayObject3D class, you need to add velocity and acceleration in the same way. Having these values available to you from the DisplayObject3D class lets you give an object acceleration, velocity and mass. The next step is to write a program that utilizes these
326
Chapter 9: Incorporating 3D Physics physical properties, such as one that creates orbiting planets. The code snippet below determines the gravitational acceleration required to place your planets in orbit. The code can be divided into three parts.
Add properties Add properties minMass, maxMass, and gravitationalConstant: //Additions private var private var private var
for gravity minMass:Number=2; maxMass:Number=20; gravitationalConstant:Number=1;
Iterate the routine Iterate the routine to determine the gravitational acceleration of each particle in the x, y, and z direction. You must iterate over i and j particles. //Iterate through i and j particles for(var j:int=i+1;j
There’s one more trick you need to learn before you move on from this section, and that’s adding a preloader.
359
Part III: Building Games and Websites Adding a Simple Preloader One of the problems with modules is that when you select one, the screen is blank until it loads and this makes for a poor user experience. An easy work around is to use the ready handler method of the module loader:
The ready method listens for when a module has completed loading, and when it does it executes a method (in this case readHandler). So here’s the trick. Create a simple animated swf loader in Flash. Use the swf component in Flex to place this simple animation on the screen. Give it the instance name myLoader. When you click on the XML-driven data grid to select a module you run the showInfo() method, which starts loading your module and makes your swf loading animation visible: //Click Handler for the datagrid private function showInfo(event:Event):void{ //Load the individual examples opaqueBackground=0; loader.url=myDataGrid.selectedItem.address; loader.loadModule(); myLoader.visible=true;}
When the module has completed loading the ready readHandler is executed which makes your animated swf invisible: //Load Handler private function readHandler(event:Event):void{ myLoader.visible=false;}
You obviously can do much more with this, but this shows you the basics of making a simple module loader. Later in this book, you learn how to work more dynamically with Flash in Flex. To learn more about working with modules check out the book’s website. There’s an entire video tutorial set on how this application was built.
AS3Dmod Early in the development of PV3D 2.0 Bartek Drozdz of Everyday Flash built a bend modifier, which later blossomed in to AS3Dmod. AS3Dmod gives you the ability to add seven modifiers to PV3D primitives: bend, noise, skew, taper, bloat, perlin, and twist. And it features an abstract layer and simple plug-in architecture that allows it to work with most Flash 3D engines. You can obtain AS3Dmod from Google Code at http://code.google.com/p/as3dmod/. When you’re there just click on the source tab and download the code from SVN just as you did with PV3D in Chapter 2.
360
Chapter 9: Incorporating 3D Physics As Bartek already has a full tutorial on how to use his modifiers you won’t cover any basic example here but you take a look at how to port a more advanced example. To get started, first check out his tutorial at http://code.google.com/p/as3dmod/wiki/AS3Dmod_Tutorial.
Understanding how the modifier stack works is the most important element of AS3Dmod.
The Heart of AS3Dmod At the heart of AS3Dmod is the modifier stack. It’s the first thing you import and instantiate. According to Barteck’s tutorial: The modifier stack is a link between the object that we want to modify and the modifiers themselves. The modifier stack is a central class of the whole AS3Dmod library. It’s important to note that modifiers can’t be applied directly to objects; everything is handled by the stack. For example, consider creating a modifier stack for a plane where mstack is the Modifier Stack instance name: mstack = new ModifierStack(new LibraryPv3d(), plane);
This important line creates the stack by providing it with two arguments. The first one indicates the 3D library you’re using. The second argument is the 3D object that the modifiers will be applied to. In the case above you pass the plane created before. AS3Dmod works with any 3D object, including all the primitives as well as with different imported types like a non-nested Collada DAE file. When you’ve created the modifier stack, you can add effects to your primitive. Noise is a good example. After importing the Noise AS3Dmod class, make an instance of it, and add it to your plane in the following way: var noise:Noise = new Noise(20); mstack.addModifier(noise); mstack.apply();
In the first line you create a modifier: noise in this case. The first argument indicates the force of the effect: 0 means no deformation at all, while a value somewhere around a 200 (in this case) creates a very strong deformation. When you’ve created the modifier you need add it to the stack. That’s the only way it can be applied to the 3D object. The last line in the code above is of key importance. It tells the stack to apply ALL the modifiers to the 3D object: mstack.apply()
That’s the heart of AS3Dmod, and it’s beautifully simple. To learn more about the basics check out Bartek’s tutorial at the link given above. Now, you build National Treasure.
Making National Treasure: Porting You’ll recall in the movie, National Treasure, stealing the Declaration of Independence was at the core of the film’s action. Creating a realistic looking Declaration of Independence requires that you apply a bend modifier to the plane that holds its image, as shown in Figure 9.16. But before you start coding it’s
361
Part III: Building Games and Websites always a good idea to see if anyone else has done a similar program, and if he or she is willing to help you out. Who knows, someone may have already released the code on the web, saving you hours of work – it pays to do your homework before you code.
Figure 9-16
Luckily, Bartek has already created the solution in Away3D, so porting it to PV3D shouldn’t be too difficult, right? Porting means to take code from one code set, such as Away3D, and merge it with a different code set, such as PV3D. You’re moving it to a different software environment. This may sound much easier than it really is; both Away3D and PV3D are very similar, but there’s enough difference between the two that you might find yourself doing some development work to get a successful port. For example, Bartek’s Away 3D version uses a double-sided plane, but PV3D doesn’t have a double-sided plane. So to do a successful port you would have to develop a double-sided plane. Luckily, we developed one in Chapter 3 of this book. Another issue is that Away3D’s lookAt method uses a 3DNumber where PV3D’s lookAt method uses a DisplayObject. So you have to place a dummy display object at the Away3D number to get PV3D to look at it. How do you find all this out? Well the best place to start is Adobe Flex. Flex has superior error checking and let’s you roll over code names, with a ctrl (PC) or command (Mac), and navigate to those elements to check errors, code, and connections. As errors pop up in your port you can quickly navigate to them and fix them. Using Flex you find yourself porting code more rapidly. There’s one more important thing about porting. There’s a trade off of time versus perfection. If an element doesn’t exist from one program to the next you’re going to have to create it and at this point it really depends on how much time that’s going to take you. You might find it easier to get a little creative and make an enhancement instead. Think about whether or not the issue can be used to your advantage, and how your particular software engine measures up compared to the one from which you’re porting . The PV3D Declaration of Independence port is in the book’s chapter code and has two solutions: the solution for a one-sided plane with perlin wind effects, and a double-sided bending plane. If you didn’t get all of this don’t worry, it’s handled on the book’s website.
362
Chapter 9: Incorporating 3D Physics
Summar y In this chapter, you examined a number of approaches to bring physics into PV3D. You started by creating a spring camera, and beefing up the DisplayObject3D class to add gravity effects to create orbiting planets. You created custom classes for oscillation and learned how to make particles interactive using the interactive scene manager (or ISM). You discovered how to build large-scale applications using states, modules, and porting. You examined both WOW and Jiglibflash physics engines. Finally, you built a Jiglib Hello World example and a Jiglib example viewer.
363
Building 3D Games for Wii It’s no coincidence that the chapter on physics precedes the chapter on gaming. Characters and objects obeying the laws of physics is a must for creating realistic games. Having a good physics engine can save you tons of coding. In this chapter you expand your knowledge of Jiglibflash even further. But no physics engine is perfect, so in order to build the games presented in this chapter you need to hack the PV3D and the Jiglibflash engines. You’re going to build a 3D pool “shooter” and Pong: In the pool game the table is a 3D bounding box and your stick is a ray gun. You navigate around the 3D bounding box using your arrow keys while shooting the balls into the corner pockets with your ray gun. If you shoot a ball three times without getting it into a pocket it explodes. Pong is a great example of a multilevel Wii game that uses artificial intelligence to compete with a single player. With each new level the computer competitor gets a little smarter. Most Flash player games are built in Flash, but in this chapter you build the pool game in Flex and Pong as an ActionScript package. You discover just how powerful Flex is in building games and how to bring Flash content into Flex and control movie clip animation. Finally, you go below the surface of many of theses classes and hack them to make needed improvements to build your game. Chances are that the developers of PV3D and Jiglib didn’t have your particular game in mind when they created their 3D engines. To custom design your game, you’ve got to hack. There are a number of techniques demonstrated in this chapter that aren’t found anywhere else. So put on your hacking shoes, you’re going to need them in this chapter.
Part III: Building Games and Websites
Shooting Pool in Space You’ve probably been amazed by images from space, showing astronauts floating in space performing gravity-free tasks. One such task would be playing 3D pool where the balls aren’t confined to a table, but bound inside a rectangular box. You don’t have to be an astronaut to play this game – you can play it in virtual space using PV3D and Jiglibflash. Like most Flash games, this one starts with a splash screen where you click on a button to start the game and set the focus from the browser to the Flash application as shown in Figure 10.1.
Figure 10-1 This game sounds easy enough, shooting balls into corner pockets in 3D space, it shouldn’t be too hard to create! But even this simple game requires a number of parts:
366
❑
Five Game States — Start, Play, Win, Lose, Cancel
❑
Skybox Custom Class
❑
Jiglib Bounding Box
❑
Timer Function
❑
Keyboard Control and Navigation around Bounding Box
❑
Mouse Control
❑
Weapon (model, sight, Flash animation, and holder)
❑
Camera Set Up
❑
Audio States Engine
❑
Ball Creator and 3D Rack
❑
Stats for Optimization
❑
Hit Method with Ball Tracking
Chapter 10: Building 3D Games for Wii ❑
Scoring System and Score Board
❑
Animation Loop
❑
Bullet Imprint on Balls
In the following sections, you first proceed through the individual parts, building up the game, and afterwards learn to optimize its performance for the web. It should be noted that Andy Zupko created a shooting experiment at http://blog.zupko.info and in the development of the game in this chapter we use a number of his ideas to create the weapon container, bullet hole, tracer, and animated muzzle flash. Andy is part of the PV3D core team and his site features the more complex technical concepts of PV3D.
Five Game States: Star t, Play, Win, Lose, Cancel Your game has five possible states: start, play, win, lose, and cancel. Generating these states in Flex can be accomplished in three ways: ❑
Popup Manager
❑
Custom Components
❑
States
Both the Popup Manager and Custom Components instantiate custom components (just as you would instantiate elements from your Flash Library). As custom MXML components are classes, they’re treated as classes. For example, if you generate a StartWindow component that extends the Panel container, you can bring it to your stage using the following code snippet: import panel.StartWindow; //StartWindow is in the panel folder private startPanel:StartWindow; //Data type startPanel as StartWindow startPanel=new StartWindow(); //Instantiate startPanel scene.addChild.startPanel; //add to your PV3D scene or pv3dCanvas.rawChildren.addChild(startPanel); //add to MXML canvas
In the last line of code you have the option of adding your panel to your PV3D scene or to your pv3dCanvas using rawChildren. Using this technique you can add as many panels as you want, and using the PopManager you can manage their display with the addition of modal control. You use the same approach with Custom Components; the only difference is that you remove the PopUpManager and you lose modal control.
What’s Modal Control? Modal control blocks user interaction with the rest of your application and indicates this by blurring the application background. But if you’re used to JavaScript modal there’s an important distinction; the Flash player runs on a single application thread and doesn’t pause! Code blocks – once started – always
367
Part III: Building Games and Websites execute to their end. So even though modal control blocks the user ’s interaction, it doesn’t stop ActionScript from executing in the background. To do that, you need to write a little extra code to control what’s happening behind your modal blur. Once the popup has been closed, the modal blur goes away and you can again interact with the main application. But there’s another problem that arises with custom components. They don’t naturally transfer data to your main MXML application. You need to use metadata and a custom event that holds a generically typed Object variable to pass data from your custom component to your main MXML application. Another option is to use a central event dispatcher, a centralized class that extends the Flex EventDispatcher class, then have the various elements of the application listen for events on the central dispatcher. For a small application such as this, use a stateful design pattern. Programming states is very easy. All the code can be placed in just one single MXML file and all the components talk to each other without the use of metadata or events.
Adding a Background Image One easy trick to creating a splash screen background is to add an image to your mx application tag using the backgroundImage property. The added background image then forms the background for all your states.
In addition, you want to set the layout to vertical so your stage is centered, and set the verticalScrollPolicy and horizontalScrollPolicy values to off. Flex automatically spawns scroll bars; you should turn them off when not needed to avoid extra drain on your CPU resources.
Using View States View States are created in Design View and give you the ability to: ❑
Make Incremental Changes
❑
Add and Remove Components
❑
Change Properties and Styles
❑
Change Event Handling
Most flashmaticians use View States to make minor changes to applications, such as tweened drop down boxes. But View States can be used to create robust application navigation. They’re ideal for game programming since games have different states and each game state can be placed into a different View State. To create the states for your game, first create your Flex MXML application and open it up in design view. Then open up the View State panel, and add a View State for each one of your game states as shown in Figure 10.2.
368
Chapter 10: Building 3D Games for Wii
Figure 10-2 The base state is generated automatically and many Flash applications use this as their starting state – don’t do that! Use the base state to carry common graphical elements that occur in most of your states and then tailor each state as needed. This lets you get under all your states and add and subtract global assets as needed. Creating a starting state is easy. Just create a new state, introState in this case, right click on it and choose start state. In Chapter 16, you rebuild this game in Flash Catalyst using the new Flex 4 ViewStates architecture. Besides that the rest of the coding and approach shown below is solid. As you create your view states, the code for your states generates automatically on the source side of your MXML application:
An important tip is to place a canvas component on your stage, then size it to 900x700, give it the id “stateCanvas”, and then vertically center it in the main application tag: doing this ensures that your states are centered on the Flex stage. Trying to center states after you’ve created them is difficult – center first! Another important tip is to make sure that you’re editing the right state. It’s a frustrating mistake to make changes to the wrong state. In design view make sure that you’ve clicked on the state you’re editing before you make changes. This sounds really simple, but it’s a common mistake. If you make this mistake, don’t try to fix it in code view. Go to design view and use control-z or manually add or delete graphical elements. States can be a little tricky, but they’re easy to create and work with and just require a little experience in figuring out where the pitfalls are.
369
Part III: Building Games and Websites Filling Your States Now that you’ve created your states, all you need to do is to start dragging the appropriate graphical elements into those states. Here’s what each state requires: ❑
base state — add pv3dCanvas, add score board
❑
introState — delete pv3dCanvas, add a panel container
❑
playState — everything comes from the base state
❑
winState — delete pv3dCanvas, add a panel container, from the score board drag the score and timer textboxes into your panel container and delete the rest of the score board
❑
loseState — delete pv3dCanvas, add a panel container, from the score board drag the score and timer textboxes into your panel container and delete the rest of the score board
❑
cancelState — delete pv3dCanvas, delete score board, add textbox
Once your graphical elements are set on the screen it’s time to go to code view and start programming them.
Programming Panel Containers Programming the panel containers is just like programming any application in Flex, with the exception that the panel is contained in a state. So you need to first open up code view and navigate to the state where the panel is. In this section, you create the intro and win state panels shown in Figure 10.3. In Chapter 16, when you rebuild this game using Flash Catalyst you’ll create these same panel states from Photoshop graphics and the Flash Catalyst HUD (Heads Up Display).
Figure 10-3 In this chapter, you use the standard Flex panel containers, but in Chapter 15 you learn how to skin containers and make them look more dynamic.
Intro State Panel After navigating to the introState panel make the following changes:
1.
370
Add a background image, title, and creationComplete method (initP1) to your mx panel tag and remove the vertical and horizontal scroll policy.
Chapter 10: Building 3D Games for Wii 2.
Center your panel using the following code snippet of the initP1 method:
startPanel.x=stateCanvas.width/2-startPanel.width/2; startPanel.y=stateCanvas.height/2-startPanel.height;
3.
Drag a single button onto your panel container, give it the label Start Game, and add the following click handler. When the code is executed it sends you to the playState using the currentState method, initiates PV3D, and changes the music state using the myGameState method.
//Move to Play State, Initiate PV3D, and Start Music private function cancelClickHandler(event:MouseEvent):void { //Go to playState currentState=”playState”; initPV3D(); myGameState(1); }
4.
Finally, add a text and text area box to the stage and place the game play instructions in those boxes. The complete code for the startGame panel is given here:
(continued)
371
Part III: Building Games and Websites (continued)
Next, you create the win panel, which is similar to the lose container, the only difference being that one says you win and the other says you lose.
Win and Lose Panels Navigate to the win panel and make the following changes:
1.
As you did with the start game panel add a background image, centering method (initP2), and title.
2. 3.
Drag two buttons to the stage and give one the label Play Again and the other Quit.
4.
Create a click handler for your Play Again button named cancelClickHandler2 which runs the restartParams() method. This method contains all the parameters required to restart your game, which includes a rack method that re-racks your balls in 3D. Finally, create a quit function method which sends you to the cancel state. This is the simplest state and requires that you set currentState = cancelState.
That’s all there is to it. The entire code snippet for the win panel is provided here:
Now that you’ve got your states and panels completed, it’s time to build a custom skybox.
Building a Custom Skybox Skyboxes are really useful for creating cool-looking enclosures for your games. But unlike Away3D, PV3D doesn’t have a native skybox in its primitive set. A skybox is easy to make: it’s just a large doublesided cube that you sit inside of. So instead of creating a new skybox each time you need one, it’s better to create a skybox class, which extends the standard cube primitive. So whenever you need a skybox you just instantiate it using the following code: //Skybox import Statement import org.lively3d.jiglib.Skybox; //Data type your skybox private var mySkyBox :Skybox; //Instantiate your SkyBox mySkyBox=new Skybox(); scene.addChild(mySkyBox);
373
Part III: Building Games and Websites You’ve already seen a number of custom classes and have dealt with building a skybox before. We therefore only give the document code for the skybox below: package org.lively3d.jiglib { //PV3D import statements import org.papervision3d.materials.BitmapMaterial; import org.papervision3d.materials.utils.MaterialsList; import org.papervision3d.objects.primitives.Cube; //Public class extends the cube public class Skybox extends Cube { //Embed your image assets [Embed (source=”/./assets/skybox/top01.jpg”)] private var BMTop: Class; [Embed (source=”/./assets/skybox/side10.jpg”)] private var BMBottom: Class; [Embed (source=”/./assets/skybox/side08.jpg”)] private var BMFront: Class; [Embed (source=”/./assets/skybox/bottom02.jpg”)] private var BMBack: Class; [Embed (source=”/./assets/skybox/side09.jpg”)] private var BMRight: Class; [Embed (source=”/./assets/skybox/top02.jpg”)] private var BMLeft: Class; //Skybox Constructor public function Skybox() {var bmTop: BitmapMaterial = new BitmapMaterial(new BMTop().bitmapData); var bmBottom: BitmapMaterial = new BitmapMaterial(new BMBottom().bitmapData); var bmFront: BitmapMaterial = new BitmapMaterial(new BMFront().bitmapData); var bmBack: BitmapMaterial = new BitmapMaterial(new BMBack().bitmapData); var bmRight: BitmapMaterial = new BitmapMaterial(new BMRight().bitmapData); var bmLeft: BitmapMaterial = new BitmapMaterial(new BMLeft().bitmapData); //Set your material to double side so you’ll see the inside of your skybox bmTop.doubleSided = true; bmBottom.doubleSided = true; bmFront.doubleSided = true; bmBack.doubleSided = true; bmRight.doubleSided = true; bmLeft.doubleSided = true; //Create your material list
374
Chapter 10: Building 3D Games for Wii var materials: MaterialsList = new MaterialsList(); //Add Your Materials to your Materials list materials.addMaterial(bmTop, “top”); materials.addMaterial(bmBottom, “bottom”); materials.addMaterial(bmFront, “front”); materials.addMaterial(bmBack, “back”); materials.addMaterial(bmRight, “right”); materials.addMaterial(bmLeft, “left”);
// Add your materials, large sides, and low segments to your super class super(materials,10000,10000,10000,8,8,8); }}}
Using this code you can quickly instantiate a skybox for any game. You’re now going to build a Jiglib bounding box for your 3D balls. Note: Seb Lee-Delisle of Plugin Media has a similar skybox class.
Building a Jiglib Bounding Box In order to “shoot” pool in space, you’ve got to have a rectangular bounding box (shown in Figure 10.4) that keeps your balls from bouncing away. Creating such a bounding box in Jiglib requires that you hack the PV3D Jiglib plugin.
Figure 10-4 Start by opening up the Papervision3DPhysics class found in the jiglib/plugin/papervision3d folder. Next, make a copy of the createGround method and rename it createPlane. Then add the ability to rotate and reposition the plane in x, y, and z as shown in the code here: public function createPlane(material:MaterialObject3D, width:Number, height:Number, position:Number3D, rotation:Number3D):RigidBody { var ground:Plane = new Plane(material, width, height); scene.addChild(ground); var jGround:JPlane = new JPlane(new Pv3dMesh(ground)); jGround.movable = false;
(continued)
375
Part III: Building Games and Websites (continued) jGround.setOrientation(JMatrix3D.rotationX(rotation.x*convPI+Math.PI/2)); jGround.rotationX = rotation.x; jGround.rotationY = rotation.y; jGround.rotationZ = rotation.z; jGround.x = position.x; jGround.y = position.y; jGround.z = position.z; jGround.friction = .2; jGround.restitution = .8; jGround.movable = false; addBody(jGround); return jGround; }
Using this modification you can now write a createBoundingBox method, which receives material, dimension, and position as shown in the code here: public function createBoundingBox(material:MaterialObject3D, myWidth:Number, myDepth:Number, myHeight:Number, myX:Number=0, myY:Number=0, myZ:Number=0):void{ //Top Bottom createPlane(material, myWidth, myDepth, new Number3D(myX,myHeight/2+myY,myZ), new Number3D(-90,0,0)); createPlane(material, myWidth, myDepth, new Number3D(myX,-myHeight/2+myY,myZ), new Number3D(90,0,0)); //Left Right createPlane(material, myHeight, myWidth, new Number3D(myX,myY,-myDepth/2+myZ), new Number3D(0,180,90)); createPlane(material, myHeight, myWidth, new Number3D(myX,myY,myDepth/2+myZ), new Number3D(180,180,270)); //Front Back createPlane(material, myDepth, myHeight, new Number3D(myWidth/2+myX,myY,myZ), new Number3D(180,270,0)); createPlane(material, myDepth, myHeight, new Number3D(-myWidth/2+myX,myY,myZ), new Number3D(180,90,0)); }
Using this method makes it pretty easy to create a bounding box of any size and material in your application just by executing a single line of code: physics.createBoundingBox(materialBox, myWidth, myDepth, myHeight);
Finally, open up Photoshop and make a transparent glass-like material for your bounding box and darken the corners for pockets. The resulting bounding box (or 3D pool table) is shown in figure 10-4.
376
Chapter 10: Building 3D Games for Wii
Creating Your Game Elements You need a large skill set in order to build 3D games in PV3D. On the one side you have to program in AS3 and on the other you have to 3D model. Building game elements is what makes working with PV3D really sing. That means becoming a good 3D artist, an essential skill in PV3D game creation. You have to create five game elements for this game: gun, muzzle flash, tracer, bullet imprint, and balls. You now walk through the creation of each one step by step along with some of the programming required to make them do their stuff!
Building a Ray Gun Modeling a simple ray gun can be done in a variety of modeling packages. This particular gun was modeled in 3DSMax and converted into an MD2 model using Milkshape (as described in Chapter 5 on modeling). Once you’ve completed modeling your gun in 3DSMax you must add a texture to it by adding an unwrap UV modifier, selecting all faces, editing, and rendering the UVW template in the Edit UVWs panel. The flattened map is shown in Figure 10.5.
Figure 10-5
Taking this map into Photoshop and applying a texture to it brings your gun to life. Once completed, export your model as a 3D studio file and then convert it to a MD2 model using Milkshape for import into PV3D. Converting to MD2 wouldn’t be necessary if the Collada format was working in 3DSMax, but in a few versions of 3DSMax it doesn’t work with PV3D. So in this case MD2 was chosen. Once your model is imported into PV3D it doesn’t matter what format you brought it in as vertices are vertices! In addition to using Milkshape you could have used one of the plugins developed by the author for 3DSMax or Blender (see the book website for more details). Once your model is imported into PV3D, it’s pretty easy to instantiate it. As shown in the code below, the MD2 model along with its skin is added to a display container named weaponContainer. This container lets you position your weapon and rotate it toward your target using the lookAt method. The
377
Part III: Building Games and Websites weaponContainer is placed into a viewport layer and given the highest layer number (100). This places the weapon container on the top layer. And within this layer you can create child layers to sort all the elements that exist inside the weapon container, as shown here. var myCarSkin:BitmapFileMaterial = new BitmapFileMaterial(‘assets/model3d/ rayGun.jpg’, true); weapon.load(‘assets/model3d/rayGun4.md2’, myCarSkin, 10, 3); //Create a weapon container weaponContainer = new DisplayObject3D(); //Scale weapon weapon.scale = 0.3; //Add weapon to a container and orient it weaponContainer.addChild(weapon); weapon.rotationX=180; weapon.rotationY=90; weapon.y=-30; weapon.x=50; //Weapon container to scene scene.addChild(weaponContainer); var weaponVPL:ViewportLayer = new ViewportLayer(null, weaponContainer); weaponVPL.layerIndex = 100; weaponVPL.sortMode = ViewportLayerSortMode.INDEX_SORT; weaponVPL.getChildLayer(weaponContainer, true, true).layerIndex = 8; viewport.containerSprite.addLayer(weaponVPL);
So what’s a viewport layer? Early on in PV3D, one of the issues people had was of conflicting display objects due to sorting issues. For example, if you tried to put a house on a ground plain, the ground plain would lose half its triangles or disappear altogether. The solution to this was to create two viewports and put your ground layer in one and your house in another. But creating a new viewport each time you needed to layer objects became tedious and thus viewport layer was born. Viewport layer is a convenient mechanism that lets you layer sets of objects easily, thus avoiding triangle-sorting problems. You can also use the quadrant render engine, which was ported over to PV3D from Away 3D. But you take a huge performance hit with it. So for now, stick with the viewport layer approach. To see more examples of how the viewport layer mechanism is used, open up the Jiglibflash modules in the examples viewer created in the previous chapter (or consult the book’s website). Now you add some animation to your muzzle.
Animating Your Muzzle Animating swf skins gives you the ability to create a muzzle flash and exploding balls. First open up Flash and create a timeline animation of your muzzle flash as shown in Figure 10.6.
378
Chapter 10: Building 3D Games for Wii
Figure 10-6 Once you’ve created your timeline animation, place it on one movie frame in the Flash Library. Using the properties panel (Export for ActionScript), name the class LaserFlash3 and export it. Now generate your swf and place it in your assets/swf folder. You can now embed this swf in Flex using the following code snippet: [Embed(source=”assets/swf/laserFlash3.swf”, symbol=”laserFlash3”)] public var muzzleFlashAsset:Class;
You can use this swf as an animated skin on a PV3D plane, which you can translate in 3D space. muzzleFlash = new Plane(new MovieMaterial(new muzzleFlashAsset(), false, true), 50, 50, 1, 1); weaponContainer.addChild(muzzleFlash); muzzleFlash.x = 40; muzzleFlash.y = 12; muzzleFlash.z = 30;
There’s a subtle trick here; you add your muzzle flash to your weapon container and position it in front of your gun. Then you put it on a lower child layer than your gun so it looks like it’s in front of your gun. weaponVPL.getChildLayer(muzzleFlash).layerIndex = 7;
When you rotate the weapon container using the lookAt method, the gun and the muzzle flash rotate together, because they’re children of the weapon container. And as your muzzle flash is on a lower child layer than the weapon, it always looks as if it’s in front of your weapon.
Making a Tracer Creating a bullet tracer is a nice effect that you accomplish by using the LineOut class created by Andy Zupko (and distributed freely on his website). The LineOut method uses PV3D’s Line3D class to draw a line from your muzzle flash to your target crosshairs each time a bullet (or ray) is fired. The line is then
379
Part III: Building Games and Websites faded and removed from the scene when alpha equals zero. In the main program, the tracer (its viewport layer) is given a blur using a blur filter to give it an atmospheric effect. lineLayer.filters = [new BlurFilter(8, 8, 1)];
It’s important to note that Flash filters also obey the optimization rules of mipmapping – that is – that their filter parameters are optimized for exponential powers of 2.
Creating Pool Balls with Bullet Imprint Creating a pool ball is simple enough – you just create a PV3D sphere and put a material on it – right? But there’s a twist; you want your pool balls to show your bullet’s imprint after it’s been shot. This means drawing the imprint of the bullet on the skin of your pool balls dynamically. But there’s nothing to draw to. So you need to pair your ball skin with a drawing skin. Here’s how it is accomplished: Add your pool ball material to a sprite, and add the sprite to a MovieMaterial with a drawing rectangle. You then make your material interactive and add a hit listener to it. var sprite:Sprite = new Sprite(); sprite.addChild(myBallArray[i]); var mat:MaterialObject3D = new MovieMaterial(sprite, false, false, false, rect); mat.interactive = true; var pv3dBox:Sphere = new Sphere(mat, 20, 8, 6); pv3dBox.addEventListener(InteractiveScene3DEvent.OBJECT_RELEASE, hitCube); scene.addChild(pv3dBox);
Each time your pool ball’s material is hit, a hole is drawn on it using the drawBitmap method shown in the following code snippet: //draw the bullet hole var mat:MovieMaterial = event.renderHitData.material as MovieMaterial; var hole: Bitmap = Sprite(mat.movie).addChild(new bulletAsset()) as Bitmap; hole.blendMode = “overlay”; hole.x = event.renderHitData.u-hole.width*0.5; hole.y = event.renderHitData.v-hole.height*0.5; //Use drawBitmap to draw the hole mat.drawBitmap();
The material that you draw to your pool ball is shown in Figure 10.7 and was generated using Flash spray paint.
Figure 10-7 And by using the “overlay” blend mode your holes are made to blend into your pool balls naturally.
380
Chapter 10: Building 3D Games for Wii Finally, you create the pool balls themselves and place them in a jBall array (for control as a particle system). The full code snippet is given here: // SETUP BOXES jBall=new Array(); //materialBox.addMaterial(mat,”all”); var rect:Rectangle = new Rectangle(0, 0, 128, 128); //RackSphere var sphereMaterial:WireframeMaterial = new WireframeMaterial(0xFFFFFF); rackSphere = new Sphere(sphereMaterial, mySize, 4, 3); myNum=rackSphere.geometry.vertices.length; //Create your Pool Balls for (var i:int = 0; i < myNum; i++) { var sprite:Sprite = new Sprite(); sprite.addChild(myBallArray[i]); var mat:MaterialObject3D = new MovieMaterial(sprite, false, false, false, rect); mat.interactive = true; var pv3dBox:Sphere = new Sphere(mat, 20, 8, 6); pv3dBox.addEventListener(InteractiveScene3DEvent.OBJECT_RELEASE, hitCube); scene.addChild(pv3dBox); //Set Ballphysics jBall[i] = new JSphere(new Pv3dMesh(pv3dBox), 20); jBall[i].moveTo(new JNumber3D(rackSphere.geometry.vertices[i].x, rackSphere.geometry.vertices[i].y, rackSphere.geometry.vertices[i].z)); //Start position RigidBody(jBall[i]).myName=jBall[i]; PhysicsSystem.getInstance().addBody(jBall[i]); PhysicsSystem.getInstance().setGravity(new JNumber3D(0,0,0)); RigidBody(jBall[i]).mass=.2; //link the skin with the box. You can use jiglib to retrieve this, but this way is faster. skins[pv3dBox] = jBall[i]; }
In addition to creating the pool balls, you also give them mass and set their gravity to zero so they can be racked and shot.
Racking Pool Balls Racking balls is accomplished by pinning them to a wireframe structure (just as you did in the previous chapter). But you don’t want your wireframe to show, so you don’t add your rack structure to your scene. //RackSphere var sphereMaterial:WireframeMaterial = new WireframeMaterial(0xFFFFFF); rackSphere = new Sphere(sphereMaterial, mySize, 4, 3); myNum=rackSphere.geometry.vertices.length;
381
Part III: Building Games and Websites When your wire frame is in place you then run the rackBalls method below to pin your pool balls to the nodes of your rack using the geometry.vertices method: private function rackBalls():void{ //Iterate over your racking sphere for (var i:int = 0; i < myNum; i++) { jBall[i].moveTo(new JNumber3D(rackSphere.geometry.vertices[i].x, rackSphere.geometry.vertices[i].y, rackSphere.geometry.vertices[i].z)); // start position jBall[i].hitNumber=0; jBall[i].movable=true; }}
But there’s an important point to make here – even though you couldn’t see your wireframe rack you still were able to use it. Which means that even though objects are invisible your processor still sees them and processes them. Your processor actually expends one third of its resources on processing “invisible” objects (or vertices), and the rendering process, which makes them visible, takes up the other two thirds. Just because an object is invisible, doesn’t mean it’s not burning precious CPU cycles. If you don’t need it, remove it from your display list!!! This includes removing listeners as well. Finally, notice that during the racking process you set the hit number of each ball to zero (jBall[i]. hitNumber=0). This variable keeps track of how many times each ball has been hit. You use this for making your pool balls explode after a certain number of hits.
Exploding Balls In the same way as you got your muzzle flash to animate, you can make your pool balls explode. Just create an explosion animation as shown in Figure 10.8, apply it to a plane, add that plane to your scene, and run your animation every time you hit a ball more than three times.
Figure 10-8
382
Chapter 10: Building 3D Games for Wii In the code snippet below rb (your rigid body pool ball) represents the rigid body that has been hit. If it’s been hit three times then your explosion plane is displaced to where you hit the ball and the lookAt method points toward your gun container. This ensures that you always see the explosion, as it’s on a plane. Your animation and sound are then run, your score decremented, total hit number incremented, and your ball placed out of view and made immovable, as shown in the following code: if(rb.hitNumber==3){ //Move explosion to ball position and lookAt weapon Container ballFlash.x=rb.x; ballFlash.y=rb.y; ballFlash.z=rb.z; ballFlash.visible=true; ballFlash.lookAt(weaponContainer); ballFlash.visible=true; ballFlash.lookAt(event.displayObject3D); clearTimeout(flashTimer2); flashTimer2 = setTimeout(killballFlash, 300); //Play Explosion Animation ((ballFlash.material as MovieMaterial).movie as MovieClip).gotoAndPlay(1); //Play explosion sound (new popSound() as Sound).play(); //Decrement Score myScore — ; //Increment total hit number numHitSank++; //Up date Score board myText.text=”Score: “+String(myScore); //Place exploded ball far out of view and make it immovable rb.x=5000; rb.movable=false; }
In the code snippet above you updated your scoreboard. The scoreboard in this game is very simple.
Scoring Balls It’s not really a game if you don’t keep score. Though some may argue this, it turns out that losing is more important than winning (well at least some of the time). The psychological impact of losing carries two thirds more weight than winning. In game design there’s a fine balance here. If your game is too difficult, your user will get frustrated and stop playing, and if it’s too easy a similar result occurs. It’s got to be challenging, and death (or failure) must be an option. The scoreboard for this game consists of only three components: game time and game score, and show gun as shown in figure 10.9.
Figure 10-9
383
Part III: Building Games and Websites Game Time To make the game more exciting, institute a timer and limit game play using the getTimer method. There’s another important psychological impact here. It turns out that people learn timed tasks more efficiently and keep them in memory longer. So if you want your technicians to remember how to repair a circuit, play a time game – they’ll never forget it. private function gameTimer():void{ getStartTime=getTimer(); gameTime=0; }
The getTimer method gives you the number of milliseconds since your Flash application began running. By subtracting the getStartTime parameter (milliseconds from when you started playing your game level) from your getTimer (milliseconds from when your Flash app started), you always have your game time. gameTime=getTimer()-getStartTime;
The code snippet that stops your game after a minute and checks your score is given here: private function showTime():void{ gameTime=getTimer()-getStartTime; if(gameTime>60000||numHitSank==10){ //Remove processes tickTimer=false; removeSight(); yesShoot=false; numHitSank=0; if(myScoremyWidth/2-60&&jBall[i].y>myHeight/2-60&&jBall[i].z>myDepth/ 2-60&&jBall[i].xmyDepth/ 2-60&&jBall[i].xmyWidth/2-60&&jBall[i].ymyDepth/ 2-60&&jBall[i].xmyWidth/2-60&&jBall[i].y>myHeight/2-60&&jBall[i].zSeven Revolutions About Seven Revolutions .75 0 20
470
Chapter 12: Developing 3D Websites REVOLUTION 1 POPULATION -.75 0 45 . . .
The great thing about XML is that you can easily add as many tags as you want, such as a description and an introductory video. And this info can be updated without recreating the main swf. So if you want to change the rotation of a panel, you just have to change the planeYRot text node. Figure 12.7 shows the panels being positioned on the stage using XML.
Figure 12-7 The XML code used to bring in your panels and place them on the stage is given in the commented code below. You’ve already performed many of these same code tasks earlier in the book, so not much discussion is needed here. As mentioned earlier, your x and z fractional positions are multiplied by the collage radius (myRadius) to set them in place. myPanel.x = myData.revData[i].planeX*myRadius; myPanel.z = myData.revData[i].planeZ*myRadius;
In the final line of code, you initialize your system using the method toggle(0). This method brings your first image panel to the front and sets your CSIS button to its highlight. This is your starting state.
471
Part III: Building Games and Websites //Simple XML used to bring in revs data private function getXml():void{ var urlRequest:URLRequest = new URLRequest(“assets/data/revsData.xml”); var xmlLoader:URLLoader = new URLLoader(urlRequest); xmlLoader.addEventListener(Event.COMPLETE, readXml); } //E4X parsing statements private function readXml(event:Event):void{ //load your XML data into myData myData = new XML(event.target.data); createPanels(); //Create Your Panels function createPanels():void{ for(var i:uint=0; i CSIS Seven Revolutions CSIS 7 Rev Introduction by Mike Lively
Leadership is compressed. Greater connectivity across the world means broader perspectives are more important than ever before, but leaders no matter what their sector have far fewer opportunities to think beyond their short term priorities and immediate responsibilities.
POPULATION CSIS Revolution 1
By the time Christopher Columbus reached the New World, global population had reached about 500 million. By July 1, 2005, population had increased by a factor of thirteen, to 6.5 billion the majority of this growth having occurred between the end of the Second World War and the present. By 2025, global population will probably reach 7.9 billion; and by 2050, there will be around 9.2 billion people on Earth.
RESOURCE CSIS Revolution 2
Global trends in population growth, economic development, industrialization, and food production, among others, are placing increasing stress on the most precious finite natural resource: water.
TECHNOLOGY CSIS Revolution 3
Scientists have already achieved significant successes in the micro-miniaturization of sensors, activators, and actuators.
486
Chapter 12: Developing 3D Websites INFORMATION CSIS Revolution 4
The information revolution is also facilitating the decentralization and proliferation of media.
ECONOMIC CSIS Revolution 5
According to a report from Goldman Sachs, if they can consolidate conditions conducive to structural growth, the total GDP of the BRIC economies by the year 2025 could equal half the aggregate level of the G-6 countries (United States, Japan, Germany, UK, France and Italy).
CONFLICT CSIS Revolution 6
This new era of conflict can be characterized by asymmetric warfare.
GOVERNANCE CSIS Revolution 7
Increasingly, companies face the challenge of juggling a triple bottom line from shareholders, management, and the public.
6.
Add the required CSS to get your percentages working in FireFox.
7. 8.
Name your html wrapper file index and place it in your Flash folder or Flex bin-release folder. Finally, upload it to your server using a program like Filezilla and text it by disabling the Flash player and JavaScript.
487
Part III: Building Games and Websites Though you have the steps given above, which aren’t that difficult, content like this is best demonstrated using video. So make sure that you check out the book’s video on this topic. It walks you through the steps in a lively way. SEO is an art that’s ever changing. Paying attention to SEO while you build your site will save you the frustration and expense of reprogramming it later. Don’t approach SEO as an afterthought. Make it a primary goal of every web project you develop and place on the web.
Summar y In this chapter, you converted the CSIS 2D site to a 3D site. You created custom tree, cloud, and collage classes, and built a 3D navigation system from scratch using PV3D components. You examined a few importing reality checks when it comes to building websites: ❑
Using a design doc
❑
Learning Photoshop
❑
Combining 2D with 3D to get an optimized 3D experience
Finally, you learned how to optimize your website for search engines by adding html text to your htmlswf wrapper file and by using the SWFObject.
488
Par t IV: Extending PV3D and Beyond Chapter 13: Making 3D Movies Chapter 14: Taking Virtual Tours Chapter 15: Adding Services Chapter 16: Flash & Math: Exploring Flash 10 and Beyond
Part IV: Extending PV3D and Beyond In Part IV, you find out how to take PV3D to the next level of performance by using Flash CS4, Photoshop3D, Flash Catalyst, and Flash Builder. You add augmented reality and services, and examine a number of approaches to rendering 3D models in CS4.
490
Making 3D Movies When Flash was born everyone was fascinated with how easily they could make objects “tween” across the stage or morph into other objects. Such concepts as masking, timeline, and framerate were subsequently brought into the stagnant world of HTML, and a new era in web design was born. But there was a problem. Making things move realistically using the Flash timeline took some work and a sleight-of-hand referred to as “frame-skipping”. But with the advent of 3D Flash, a new era of animation has begun, one which isn’t bound by a mechanical timeline or 2D environment. In this chapter, you review many of the new tools being used in making the next generation of Flash 3D movies such as Flash Professional CS4, Photoshop 3D, Flash Catalyst, and Flash Builder. You create a number of applications to demonstrate their use, build an animation recording application, and discuss building a PV3D studio.
Houston . . . We ’ve Got a Problem Things are moving really fast in the programming world for the Adobe Flash Platform, perhaps too fast. These aren’t just code enhancements, but complete architectural overhauls that render previous codes obsolete and non-functional. It’s as if your economy was based on dollars, and one day America just starts using rupees as its major currency. Then, a week later, Americans adopt the Yen – imagine how you’d feel if you were a stockbroker by trade. The rapidly changing world of architectural methodologies and design patterns makes it difficult for developers to write software with staying power. If you paid close attention to the CSIS Seven Revolutions section in the last chapter, this is a key problem with the information age. Because technology is changing so fast it becomes difficult for planners to plan much beyond a week, and in many cases it’s day by day. This is true of software development as well; complete development schedules and projects are scrapped with each new programming architecture release. Why change architecture? Typically a new architecture release carries with it a significant performance enhancement and gives you a decisive edge over your competitors.
Part IV: Extending PV3D and Beyond If you want to “get into the business” of software development don’t start at the end of the line. Start with the newest technology (such as Flash Catalyst and Flash Builder). As everything is moving so rapidly, you can become an expert on it in less than two years. You may be wondering when it’s all going to stop – it’s not! Change is going to accelerate. It’s “infodarwinism.” Due to worldwide connectivity, you now have millions of developers (and designers) thrown into the mix, contributing to the process around the clock. So what’s the solution? Ride the rollercoaster, love what you do, and contribute to the open source community whenever possible. Grab on, and don’t let go!!! You start this chapter by examining one of the new kids on the block (a real PV3D 2.0 killer) – Flash CS4.
Using Flash CS4 When Flash 10 first came out, many of the PV3D 2.0 developers were quick to say that it wasn’t that great and that not much performance enhancement had been obtained by its release. However, Flash & Math were quick to say that their performance benchmarks improved by a factor of five as a result of these enhancements. However, frames-per-second is not the only measurement to judge by when evaluating a new framework. Usability also plays a key role. And with the release of CS4 and its native 3D tools, your development productivity can be improved by as much as a factor of ten. Oh, sure . . . you aren’t bringing in 3D Collada models, but not because you can’t, but because the classes to do so have not yet been written (see Chapter 16). And literally thousands of lines of code for 3D are now native to the Flash 10 player ’s engine, significantly bringing down the code bulk required to render a 3D object. In Flash CS4, you have a native z component that gives you the ability to give elements 3D translation and 3D rotations. You can do this using the 3D Rotation Tool and 3D Translation tool found in the Flash CS4 Tools panel, as shown in Figure 13.1.
Figure 13-1 You can find full tutorials on using Flash CS4 on this book’s web site. So instead of going through the basics of Flash CS4, you’re about to apply Flash Professional to reconstruct the CSIS Seven Revolutions that was discussed in the previous chapter. Then, you compare the development cycle of PV3D 2.0 to Flash CS4.
Rebuilding CSIS Seven Revolutions in Flash CS4 Taking the Seven Revolutions project from the previous chapter and creating an equivalent experience in Flash CS4 (as opposed to PV3D) is a real eye-opener. Figure 13.2 demonstrates the difference between the two.
492
Chapter 13: Making 3D Movies
Figure 13-2
Comparing PV3D and Flash CS4 In all fairness, the Flash CS4 application is a scaled-down version of the PV3D application (no clouds or trees). But regardless of how you slice it, two hours versus three days of development to get a similar experience really shakes the tree. While you’re fiddling around with creating classes, the competition just robbed you of your contract three days ago. That’s the power of new development tools.
Placing a TextArea on a 3D Panel In addition, as CS4 has a native z-component, all Flash components (such as textbox, buttons, and video) are readily introduced into your 3D Flash arena. Figure 13.3 demonstrates this by showing a text box at the top of the integration 5-plane panel. Using the native z component the textbox was placed in the plane. The textbox and plane are rotated together in 3D around the y-axis with no distortion to the textbox; dropping text components into 3D and rotating them as in the example isn’t easily done in PV3D. But in Flash CS4, it’s simple.
Figure 13-3
493
Part IV: Extending PV3D and Beyond In addition, as a result of the native-z component in Flash CS4, you can construct your navigation system without writing a single line of code.
Building Your Navigation Panel The navigation panel in Figure 13.4 consists of eight buttons that are aligned in a row. In Flash CS4, you can convert your images to buttons by choosing Modify on the main menu bar, Convert to Symbol . . . and Button from the Type menu. And the buttons are styled (as shown in Figure 13.4) with a bevel effects found in the properties panel. All of this without a single line of code and all in 3D (or with a native z component)!
Figure 13-4 You create the button states when converting your images to buttons and tool tips are easily created by putting text in the roll over frame (or state) of your button as shown in Figure 13.5. Rolling over each navigation button causes the button to lift, change color, and its text tooltip to appear. This behavior is created using the typical Flash button time line shown in 13.5, but now it’s in 3D, which means you can translate it and rotate it in x, y, and z.
Figure 13-5 The buttons are controlled using listeners as shown in the following code snippet: btn0.addEventListener(MouseEvent.CLICK, clickSelection);
494
Chapter 13: Making 3D Movies As each button is clicked on, a clickSelection method is fired which identifies the button based on its name using its target.name property (evtObj.target.name). This name is sent to a switch statement, which selects the appropriate TweenLite method based on the button’s name. //Button Name Variable var mySwitchVar:String=evtObj.target.name; //Switch Case (Only two of eight cases shown) switch(mySwitchVar) { case “btn0”: //Start State TweenLite.to(mySlideLayer, 1.2, {x:1735, y:790, z:-441,rotationY:-7}); TweenLite.to(bkGnd, 2, {x:1735-cntr}); break; case “btn1”: //Rev 1 - tween to position TweenLite.to(mySlideLayer, 1.2, {x:565, y:522, z:-77,rotationY:-7}); TweenLite.to(bkGnd, 2, {x:765-cntr}); break; . . . default: trace(“Not in data”); break; }
A similar saving was obtained when creating the sliding display object that’s shown in Figure 13-6.
Creating a Sliding Display Object The image panels in Figure 13.6 all exist in a single movie clip with the instance name mySlideLayer. Each of the TweenLite methods discussed above contains the appropriate coordinates that orient the mySlideLayer Movie Clip into the correct position to bring the appropriate panel to its correct location. The appearance of the image panels in the mySlideLayer movie clip (in Figure 13.6) is a little deceptive. It looks as if the image panels have been placed side by side, with the alternating panels sized smaller in x and y. But actually, these smaller-looking panels have been pushed back in the z direction.
Figure 13-6 So what you’re actually seeing is a change in depth. In fact, the image panels (in Figure 13.6) could have been placed anywhere with any orientation using the translation and rotation tools that were shown previously in Figure 13.1. But for simplicity of discussion, they were lined up in a row with alternating depth.
495
Part IV: Extending PV3D and Beyond Here’s an important tip: as your scenes become more complex using the orientation tools, the tools that appeared previously in Figure 13.1 will become cumbersome to use. You’ll find using the control panels that appear in Figure 13.7 much easier to use for controlling x, y, z, and rotational orientation.
Figure 13-7
Use the X, Y, and Z parameters in the 3D Position and View panel to change your translation and the Y parameter in the 3D Rotation panel to change your orientation. Don’t change the values under 3D Center point. In addition, the panel controls turn into sliders when you roll over them and you can preview and slide your image panels as you position them. 3DSMax provides a very similar functionality. Finally, this entire sliding movie clip (mySlideLayer) sits on top of the image collage. Reflection is created graphically (all in Flash) by inverting an image and reducing its alpha value. The entire application is put together on the layered time line shown in Figure 13.8.
Figure 13-8
In Figure 13.8, ActionScript code is placed on the first layer (Actions). The navigation panel is on the second layer (buttons). The sliding movie clip (mySlideLayer) is on the third layer (slidingDO). And the image collage is on the fourth layer (bckGrnd). Figure 13.9 shows all the graphical elements together.
496
Chapter 13: Making 3D Movies
Figure 13-9 The entire documented code is given here: stop(); //TweenLite import gs.TweenLite; var cntr:Number=2000; //Text for Seven Revolutions Text Area Boxes mySlideLayer.rev0.rev0txt.text=”SEVEN REVOLUTIONS is a project led by (CSIS) . . . ”; mySlideLayer.rev1.rev1txt.text=”By the time Christopher Columbus reached the New World . . . “; mySlideLayer.rev2.rev2txt.text=”Global trends in population growth, economic development . . . ”; mySlideLayer.rev3.rev3txt.text=”Scientists have already achieved significant successes . . . ”; mySlideLayer.rev4.rev4txt.text=”The information revolution is also facilitating the decentralization and proliferation of media . . . ”; mySlideLayer.rev5.rev5txt.text=”According to a report from Goldman Sachs, if they can consolidate conditions . . . “; mySlideLayer.rev6.rev6txt.text=”This new era of conflict can be characterized . . . ”; mySlideLayer.rev7.rev7txt.text=”Increasingly, companies face the challenge of juggling a triple bottom line . . . ”; //Sound var sound:Sound= new Sound(); var soundControl1:SoundChannel=new SoundChannel(); soundControl1=(new ambientSound() as Sound).play(0,100); //Handle button events btn0.addEventListener(MouseEvent.CLICK, clickSelection); btn1.addEventListener(MouseEvent.CLICK, clickSelection); btn2.addEventListener(MouseEvent.CLICK, clickSelection); btn3.addEventListener(MouseEvent.CLICK, clickSelection); btn4.addEventListener(MouseEvent.CLICK, clickSelection);
(continued)
497
Part IV: Extending PV3D and Beyond (continued) btn5.addEventListener(MouseEvent.CLICK, clickSelection); btn6.addEventListener(MouseEvent.CLICK, clickSelection); btn7.addEventListener(MouseEvent.CLICK, clickSelection); //Switch case for Buttons Navigation function clickSelection(evtObj:MouseEvent){ //Button Name Variable var mySwitchVar:String=evtObj.target.name; switch(mySwitchVar) { case “btn0”: //Start State TweenLite.to(mySlideLayer, 1.2, {x:1735, y:790, z:-441,rotationY:-7}); TweenLite.to(bkGnd, 2, {x:1735-cntr}); break; case “btn1”: //Rev 1 - tween to position TweenLite.to(mySlideLayer, 1.2, {x:565, y:522, z:-77,rotationY:-7}); TweenLite.to(bkGnd, 2, {x:765-cntr}); break; case “btn2”: //Rev 2 - tween to position TweenLite.to(mySlideLayer, 1.2, {x:448, y:783, z:-600,rotationY:-7}); TweenLite.to(bkGnd, 2, {x:448-cntr}); break; case “btn3”: // Rev 3 - tween to position TweenLite.to(mySlideLayer, 1.2, {x:-144, y:512, z:-168,rotationY:-7}); TweenLite.to(bkGnd, 2, {x:-144-cntr}); break; case “btn4”: // Rev 4 - tween to position TweenLite.to(mySlideLayer, 1.2, {x:-728, y:786, z:-746,rotationY:-7}); TweenLite.to(bkGnd, 2, {x:-728-cntr}); break; case “btn5”: // Rev 5 - tween to position TweenLite.to(mySlideLayer, 1.2, {x:-856, y:512, z:-256,rotationY:-7}); TweenLite.to(bkGnd, 2, {x:-856-cntr}); break; case “btn6”: // Rev 6 - tween to position TweenLite.to(mySlideLayer, 1.2, {x:-1984, y:780, z:-892,rotationY:-7}); TweenLite.to(bkGnd, 2, {x:-1384-cntr+400}); break; case “btn7”: // Rev 7 - tween to position TweenLite.to(mySlideLayer, 1.2, {x:-1564, y:506, z:-347,rotationY:-7}); TweenLite.to(bkGnd, 2, {x:-1564-cntr+400}); break; default: trace(“Not in data”); break; }}
You may be wondering: why study PV3D 2.0 at all? PV3D is still superior in how it handles 3D objects and the camera object. To surpass this performance, classes need to be written for CS4. That will be discussed further in Chapter 16. Finally, you need to add a preloader.
498
Chapter 13: Making 3D Movies
Adding a Preloader If a user comes to your site and doesn’t see anything, chances are that he’ll hop right off thinking it’s broken. Having something pop up immediately is essential to keeping your business. That usually means using a preloader. A preloader is a graphical way of telling your clients that your site is loading. To create a preloader in Flash just move your application to frame 2 and put your preloader on frame 1 as shown in Figure 13.10.
Figure 13-10 Preloaders can get very elaborate. The one in Figure 13.10 is as simple as they get. It’s just a dynamic textbox that holds the percentage of how much of the Flash movie has been loaded. At the core of the preloader is the loaderInfo class which lets you monitor the loading progress event. As the movie loads, the updateProgress method is updated and your loaded movie percentage is calculated. Upon loading 100 per cent, you advance to your application frame using nextFrame. The preloader code follows: stop(); //Code for Preloader loaderInfo.addEventListener(ProgressEvent.PROGRESS, updateProgress); function updateProgress(evtObject:ProgressEvent):void { //Calculate percent loader var myPercent:Number = Math.floor((evtObject.bytesLoaded*100)/evtObject .bytesTotal); loadingTxt.text = myPercent+”%”; //Go to next frame when 100% loaded if (myPercent==100){ //Go to application frame nextFrame(); } }
As easy as Flash CS4 makes it, building interactivity in Flash websites can be coding intensive and with each new release of the Flash player the chasm between developer and designer is growing. But there’s hope! Adobe has released Flash Catalyst to help bridge this growing gap.
499
Part IV: Extending PV3D and Beyond
Bridging the Gap with Flash Catalyst As wonderful as the new era of 3D Flash is, it comes with the high price of learning OOP. The transition from Flash 8 (AS2) to 9 (AS3) was phenomenal, requiring a factor of 10 in programming skills and yielding a factor of 10 in performance. But it also created a gap between designer and developer and Adobe is seeking to remedy this problem with the release of Flash Catalyst as shown in Figure 13.11.
Figure 13-11 Flash Catalyst transforms Illustrator and Photoshop designer artwork into interactive applications which can be brought into Flash Builder. As a designer creates a visual interactive experience in Flash Catalyst, code is being written in the background that can be finished off by a developer in Flash Builder. Creating a Flash application using Flash Catalyst consists of the following three steps:
1. 2. 3.
Create your visual design in Photoshop or Illustrator. Import your design into Flash Catalyst and make it interactive. Finish off your application in Flash Builder.
Taking your CSIS 7 Revs example, you start by laying it all out in Photoshop.
Laying it out in Photoshop As illustrated in Figure 13.12 you can lay the CSIS 7 Revs site in four Photoshop layers. The bottom layer is the ambient black color. The next layer is the background group which is the collage background with its reflection. The next layer group is panels, which has eight images in it. And finally the buttons layer group sits on top, with eight navigation images.
Figure 13-12
500
Chapter 13: Making 3D Movies You can lay all this out in about five minutes in Photoshop and then you’re ready to import it into Flash Catalyst. Choose Flash Catalyst Create New Project from Design File From Adobe Photoshop PSD File.
Creating Code Automatically in Flash Catalyst As soon as you’ve brought your Photoshop (or Illustrator) files into Flash Catalyst you can add interactivity and even produce a project for the web. It’s important to note that Flash Catalyst has already been writing code for you automatically. If you’re an Adobe Flex developer you’ll understand this immediately. Just as Flex automatically writes code as graphical elements are brought to the stage, so does Flash Catalyst. But Flash Catalyst goes a step further and writes methods and properties, not just MXML container code. Switching from Design to Code view, from your simple import from Photoshop you can examine the code already written in Flash Catalyst :
(continued)
501
Part IV: Extending PV3D and Beyond (continued)
resizeMode=”scale” resizeMode=”scale” resizeMode=”scale” resizeMode=”scale” resizeMode=”scale” resizeMode=”scale” resizeMode=”scale” resizeMode=”scale”
From examining this code, there are several very important points: ❑
The code has been optimized to work with Photoshop and Illustrator and even contains group layer names.
❑
Flash Catalyst is both a streamlined development tool and a teaching tool that can be used to bring developers rapidly up to speed in coding.
While working at NKU the author trained a number of graphic designers to become developers. Over a three-year period he noticed that there were two bottlenecks in the process: getting graphic designers to stop designing for print (and design for the web), and bringing them rapidly up to speed on coding. Flash Catalyst solves both of these problems. It forces designers to start designing for the web, and it enables them to see the code they generated in the process. So not only does Flash Catalyst bridge the gap between designer and developer, but it’s also a powerful teaching tool that enables teachers to show designers how to code by enabling them to see the results of their work in Flash Catalyst. ❑
If all you did at this point was bring the file generated from Flash Catalyst into Flash Builder, you’d have saved a significant amount of development time already; enough to make Flash Catalyst worth using, especially if you’re working with a team of developers.
You have a number of options with Flash Catalyst; you can add interactivity, upload to the web for your client to review, or create a file for import into Flash Builder for further development. At this point, you import your Flash Catalyst file into Flash Builder (to see a complete example of FC to FB check out Chapter 15).
502
Chapter 13: Making 3D Movies
Sending Your Work to Flash Builder Bringing Flash Catalyst into Flash Builder is an easy process. However, one of the problems with FC at the time of writing this book was that you could not “round trip.” What is round tripping? It’s taking files from Flash Catalyst into Flash Builder and then from Flash Builder back into Flash Catalyst (as you can do with Illustrator files). Round tripping between FC and FB is an important and powerful feature not yet implemented in the beta version (used in this book) of Flash Builder. Round tripping between FB and FC is within the scope of Adobe’s development plan but given its complexity still may be somewhat in the future or limited in its capability. Bringing FC files into FB is a simple two-step process:
1.
Save your Flash Catalyst file. The file saves as a FXP file, which is the same format that Flash Builder uses.
2.
Import your FXP file into Flash Builder using the “Import Flex Project (FXP)” item found in the File menu item.
On importing your project into Flash Builder you should notice that Flash Builder has created the appropriate file structure for the code generated by Flash Catalyst as shown in Figure 13.13. Specifically, the image resources are placed in an assets folder which is referenced by the @Embed tags.
Figure 13-13
You should also notice that Flash Builder has replaced the Flex Navigator panel with the Package Explorer panel. The Package Explorer is an enhanced version of the Flex Navigator that existed in Flex Builder 3. There are a number of new features in Flash Builder that you find out about in the next section (to see the completion of the project above check out the book’s website).
Pushing the Envelope with Flash Builder When Adobe purchased Macromedia, many of us held our breath in horror and choked down rumors that Adobe would kill Flash. Then Flex emerged and we all closed our eyes and hoped it would go away. And in the transition from Flash 8 to Flash 9 many of us lost huge reservoirs of code that would no longer work in the new Flash display object environment.
503
Part IV: Extending PV3D and Beyond The author had written a 60,000 line Flash 8 learning management system (comparable to the Navy’s) that would no longer function. Here are a number of new features that Flash Builder offers: ❑
Skinnable “Spark” components
❑
New namespaces and text controls
❑
New Package Explorer
❑
Moving and Refactoring of classes
❑
Automatic code generation for getters and setters and event handlers
❑
Flash Components directly from Flash Builder
❑
Customizable file templates
❑
New data connectivity tools (super easy to connect, and offers variety)
❑
Uses the SwiftObject to deploy sites
❑
Can choose project’s SDK from the initial project development screen
❑
Beefed-up view state syntax
❑
Beefed-up CSS
❑
New effects Animation Engine (any filter including Pixel Bender)
❑
Beefed-up Animation Engine (including 3D)
❑
Documentation with code hinting
❑
World-class text layout system
These are a few of the many new features in Flash Builder that enhance your work flow by a factor of 10. One big timesaver is the “Flash Components directly from Flash Builder” feature. In Flex 2 and 3 it was difficult to bring Flash animations into Flex and control them. It required embedding the swf animation files and creating a special class and placing that class in Flex. With the new Flash Components feature this entire process is streamlined and a controlling SWC file is automatically created for you in FB.
Birthing a Spark With Flash Builder comes the birth of a new component library: Spark! What is Spark? Spark is the new skinning architecture for Flex 4. Spark components separate the graphics from the code; where the old MXML components were one big lump combining both graphics and code. And why do we need a new component type? Weren’t the old components good enough? In the evolution of Flash Catalyst, the Flex SDK team realized that for Flash Catalyst to achieve its ambitious work flow that there needed to be a major overhaul of the Flex Framework: namely the creation of the Spark architecture. If you think about it for a moment it makes sense. If Flash Catalyst is all about generating graphical interfaces without code, having code in the component architecture poses a problem. In addition, Flex had another problem. It was really difficult to skin components. Using Spark it’s easy to do. Really easy!
504
Chapter 13: Making 3D Movies With Spark you have separated a component’s display logic from the component’s business logic. A Spark component consists of an object class and a skin class and by associating the two together you get a full blown Spark component. The new Spark architecture lets you apply graphic designs to your components easily. And since the code is written in MXML it’s easy to understand. An example of an MXML component file used to skin a Spark button is given here: [HostComponent(“spark.components.Button”)]
The biggest change that you see in skinning components is that there’s a new Metadata tag called HostComponent. [HostComponent(“spark.components.Button”)]
It declares the fully qualified name of the component that will use the skin. Of course, with Flash Catalyst you don’t have to skin components from scratch, and when (and if) round tripping is instituted (between FC and FB), the entire process becomes iterative without writing a single line of code.
Animating in 3D Using Flash Builder Once again, Flash Catalyst became a driving force behind some of the new animation effects created by Adobe. Essentially, Flex needed to be able to target arbitrary objects. But there’s a problem. Once you
505
Part IV: Extending PV3D and Beyond target (or point) an animation at a graphic object (as it’s not a component) you don’t have the ability to move it. It doesn’t have a move function that effects can play, so Adobe broadened the capability of effects so they could target the Spark graphical elements and other arbitrary types. As a result of these changes, Flash Builder now has a new Animate Super class, which is a subclass of Effect and takes the place of Tween effect. It’s similar to the AnimateProperty function in Flex 3, which animated one property at a time, but now you can animate multiple properties at the same time. In addition, some of the new things you can do are: repeat behavior, reverse animation, animate color, and 3D effects such as translation and rotation. As an example of how this new animation effects engine works in Flash Builder you’re going to build the animated panel shown in Figure 13.14. As you rollover each of the images in the panel, the image you roll over rotates around once and then reverses its animation. Reversing animation once was a really hard thing to do, but in Flash Builder it’s as easy as just setting a flag.
Figure 13-14 The program is very simple to create. All you essentially do is add a 3D rotation effect to each image element in Figure 13.14. The Rotate3D Spark effect, shown in the code snippet below, accomplishes the required behavior by simply setting flags:
Another ground-breaking element in Flash builder is its automatic code creation. Because each image element in Figure 13.14 needs to be made interactive, a rollover listener had to be added to each image.
Upon code completion of the rollOver, the option is given to automatically create the required listener code stub.
506
Chapter 13: Making 3D Movies protected function targetImg1_rollOverHandler(event:MouseEvent):void { if(btn1B) rotateEffect1.play(); }
As a developer you just need to complete the code stub, which in this case plays the 3D rotation effect, which spins your image and reverses that spin. Finally, in the Rotate3D effect, two flags have been set that disable and enable the effect on start and finish. Thus, the effect can’t be activated again until the entire animation has played. And that’s pretty cool, and saves you the hassle of having your animation messed up before it’s completed. The entire code for the rotating image panel follows:
508
Chapter 13: Making 3D Movies
(continued)
509
Part IV: Extending PV3D and Beyond (continued)
The preceding code isn’t optimized, but is given to illustrate how easy it is to create behaviors that in the past were very difficult to do in programs like PV3D. The entire animation program took about 30 minutes to write, and in PV3D would have taken much longer and with much more code required to do so. Finally, did Adobe kill Flash? Absolutely not – it elevated it to the next level in the competitive market place and integrated it in the powerful open source community; of which the author is an avid supporter and contributor. The hard lesson for developers is that technology is moving forward at an extreme pace and it requires much diligence to keep up. Your ability to smoothly transition as technology advances and expand your skill sets along the way will ensure your continual employment in an ever-changing technology environment. Adobe has moved Flash from being an animation engine to a full platform with a complete set of integrative technologies. It’s now on TV, the web, in Smart Homes, Phones, New York Times, Facebook, and in Hollywood movies. And there’s more. Adobe has released a very powerful set of 3D tools in Photoshop. You can even create Collada files in Photoshop for import into PV3D.
510
Chapter 13: Making 3D Movies
Going 3D with Photoshop One of the big themes of Adobe Flash Camp (Adobe’s “unconference” style training) is that “there’s more”. Adobe is really pushing the envelope of web technology, especially in the area of 3D. And not just with Flash, but a number of other integrative technologies, such as Photoshop. The CS4 Photoshop engine is totally new. You have the ability to model in Photoshop. And in addition, you can paint directly onto the surface of your 3D models (you can even paint on effects such as bump maps) – something you can’t do in 3DSMax. You no longer have to worry about UV coordinates – just paint directly on your surfaces. Photoshop also has a full lighting system, layered meshes, and a powerful ray-tracing render engine.
Getting Started Tips Here are a few getting started tips: ❑
Open up 3D objects in Photoshop in the same way that you would open up any other file. There are a number of 3D file formats that you can import: WavefrontOBJ (great for models, not for materials); U3D (good, but not as widely adopted as Collada); KMZ (huge resources available from Google Warehouse); Collada (accurate, best format), and 3DS (smoothes things, but not always accurate) files.
❑
Add 3D models to 2D files by creating a 3D layer. Choose 3D then New Layer from 3D file and select your 3D file. This creates a 3D layer that you can superimpose on your 2D layers. Also, optimally 3D files can be viewed from Adobe Bridge and brought into Photoshop just by clicking on them.
❑
Once you’ve created your file , you can save it as a psd file (for a 3D creation, it’s a selfcontained file). Exporting your 3D model to a 3D program such as Papervision3D is easily done by selecting the layer you want to export, going to your 3D menu and selecting Export 3D Layer and selecting the file format you want to export. For PV3D you choose Collada.
As opposed to trying to go through a complete tutorial on 3D Photoshop, you now go through a simple but very important example. More examples can be found on the book’s website.
Unlocking the Power of Google 3D Warehouse Google 3D Warehouse is a huge reservoir of material that can potentially be used in PV3D. But one of the problems with working with SketchUp models is getting those models into PV3D. It’s not that PV3D can’t handle most low polygon models; it’s just that getting models into the right format for PV3D to read can at times be a really big and painful chore. Two problems arise when working with SketchUp material: ❑
Many SketchUp models have too many vertices.
❑
SketchUp models are difficult to bring into PV3D.
However, using Photoshop 3D can help alleviate this problem, as any KMZ file can be readily imported into Photoshop. Once you’ve brought that model into Photoshop, you can then export it as a Collada file for use in PV3D. Or you can import it into another 3D application, as shown in Figure 13.15, where you can reduce its polygon count, retexture it, and export it for use in PV3D.
511
Part IV: Extending PV3D and Beyond Figure 13.15 shows a KMZ SketchUp lamp model that’s been imported into Photoshop and then exported as an obj file and imported into Maya. Once in Maya the lamp can be retextured, reduced, and exported for PV3D.
Figure 13-15
Recording 3D Animations Recording 3D animations in Flash is one of the most powerful but yet most underutilized areas of development in Flash. Its application in education is immense, especially in the area of student evaluation. For a long time educators have struggled with how to evaluate student learning properly. And with the advent of distance learning the burning issue of evaluation has become a central theme and yet little real progress has been made. As part of the solution Joseph Novac, Professor of Education and Biological sciences at Cornell University, introduced concept mapping (also called mind-mapping). Concept mapping is a way of graphically organizing and representing knowledge. According to Novac, concepts are perceived regularity in events or objects represented by a label (such as a word). Creating a concept map is accomplished by linking concepts with prepositions, where prepositions are units of meaning. So typically concepts are placed in boxes and the lines linking the concepts are prepositions. Novac found that by using this graphical technique he could more fully evaluate what students knew about their subject. He found that typical multiple choice questions, the mainstream of educational evaluation, were flawed. Students answered such questions by rote, and many times correctly for the wrong reasons. But by examining the complexity of a student’s concept map he could accurately predict how much the student knew about the subject. By recording animations you can take concept mapping to the next level. Recording a student making a concept map not only demonstrates what he knows about a subject, but also examines his cognitive processes. In addition, by using technology like the FMS you can get a live stream, allowing for additional audio, video, and interactive shared objects. And because it’s animation you can catalog and fast forward or rewind through the process: something that can’t be done in the traditional classroom setting.
Creating an Animation Recorder in PV3D Building an animation recording system in Flash is a simple process. And there’s a trick! You don’t have to build a screen recording that records a video of everything you do, but an application that time stamps
512
Chapter 13: Making 3D Movies what you click on. You’ve already seen this in the chapter on the FMS. When synchronizing two cars on a race track you only had to transfer the acceleration data. The program receiving that data knew what to do with it and executed the appropriate physics. So taking the concept of recording “only what you clicked on” you can create a Flash animation system by time stamping those clicks and feeding them back to a player program. The player program repeats what you clicked on in the correct time sequence, as illustrated in Figure 13.16. This is extremely easy and as opposed to recording terabytes of video screen capture data, you’re saving very small XML files that hold what you clicked on and its timestamp. Which means you can have hours of animation play back that only require a few kilobytes of storage space.
Recorder Program
XML Time Stamp
Player Program
Figure 13-16
Figure 13.17 illustrates the concept. A jet fighter is flown around the screen.
Figure 13-17
The jet motion is controlled by the following 12 key commands: ❑
Right Arrow — Turn Right
❑
Left Arrow — Turn Left
❑
Up Arrow — Move Forward
❑
Down Arrow — Move Backwards
❑
Page Up — Move Up
513
Part IV: Extending PV3D and Beyond ❑
Page Down — Move Down
❑
a — pitch plus
❑
s — yaw plus
❑
d — roll plus
❑
z — pitch minus
❑
x — yaw minus
❑
c — roll minus
To start a recording, you input a file name and press the recording button. Then you fly your jet around the screen using the key described above. Each time you press a key, the key you pressed, and when you pressed it, is recorded and sent to an XML file. (Actually all 12 key states are recorded with the time interval.) When your animation is complete you hit the save button and your animation is saved to an XML file. To play the recorded animation you just click on the name of your animation’s file and hit play. Your time stamped clicks are then sent to a player program which parses the XML animation, starts a timer and selects the buttons to be pressed “programmatically” from a switch statement (or series of if statements for simultaneous motion). The savings in producing animation in this way are tremendous. For this particular system there are 12 key states and upon each transition (key press or key release) the press state of each key is recorded, where “0” means not pressed and “1” means pressed. Having the condition of all states recorded with each individual key press allows for multiple key presses at once. So for example, you can pitch and dive at the same time by holding down the Page Down and a key simultaneously. It’s really a lot of fun! The XML data generated with each key action (press or release) are given below. It shows that with each key action the condition of all key states and the time at which that condition occurred are recorded. You’ve already seen this approach to programming before and it’s commonly referred to as a state engine. With 12 possible keys and two possible conditions for each key (press or release), the total possible states that your system can have are 2 to the 12th power or 4096 possible states. Thinking of software systems as state engines can reduce the amount of programming work you need to do. For example, as opposed to representing your number as 12 separate key press/release entities (shown in the following XML) you could also represent them as a single 3-digit hexadecimal number (B68 for example), saving a little space and work. 0,0,0,0,0,0,0,0,0,0,0,0 0 0,1,0,0,0,0,0,0,0,0,0,0 4000
514
Chapter 13: Making 3D Movies 0,0,1,0,0,0,0,0,0,0,0,0 6000 0,0,1,0,0,0,0,0,1,0,0,0 10000
The data is sent to the web using Flex’s HTTPService method. {MyTopicData} {myStateData} {myStartTime}
Each time a key is pressed or released Flex runs the setPostFile.send method which executes the HTTPService command and sends the name of the file to be accessed (MyTopicData), the key state information (myStateData), and the time of that state (myStartTime) to a PHP file. The PHP file uses DOM programming to store that data on the web in an XML file.
Storing Your Results Using the DOM So far in this book you’ve created programs that store data in a variety of ways using MySQL, AIR, and Flash 10. But every once in a while you’re going to be asked to store data to the web without the use of a database. Now at first this may sound a little ridiculous, but there are some advantages to doing so. One is that you can create maintenance-free applications (or apps that don’t require a database programmer to install). You just drop your application on the web into its folder and it runs and stores data hassle free. A number of educators who run short courses and have little knowledge of computers find such an option extremely appealing. In the Jet animation program above, all of its data are being recorded to the web using DOM programming. DOM programming is not difficult, but may seem a little unusual to you at first. In the code below a new DOM document is created and passed the variable name from Flex (myFileName), which opens an XML file to be appended with the new “jet state” data. Using the appendChild method, two pairs of nodes are inserted at the end of your XML data: one set of nodes for your key action state and one set of nodes for your begin time for that state. The createElement method creates the XML tags and the createTextNode creates the text element (or data) to be inserted between those tags. These pairs of nodes are sandwiched by keyaction tags that are created by using the createElement(‘keyaction’) method. So essentially what you’re doing is recreating the structure of the XML jet data file given above. Finally, your data is saved as XML to the web.
515
Part IV: Extending PV3D and Beyond
You have a number of ways of approaching this problem. The method above is strictly for illustrative purposes. Each programming scenario typically has a different approach depending on what’s required to optimize your code. But as you can see, programming the DOM can come in handy when you don’t want to use a database to save data to the web. And it’s commonly done; there are even entire opensource wikis that use this approach. A great place to learn more about the DOM (in addition to the book’s website) is from IBM. IBM has a number of superb web tutorials and their DOM articles can be found at http://www.ibm.com/ developerworks/library/os-xmldomphp/.
You can download the code (and other similar examples) for the jet animation project from the book’s website.
Animating Spark The Spark effect classes hide much of the underlying effects implementation to simplify the use of effects. But if you want to grab control and create your own animations you can use motion paths. What’s a motion path? It’s the collection of keyframes required to produce an effect. And it’s implemented using the AnimateTransform class. In addition to the new Animate Super class, which targets properties, there’s an animate transform effect class that supports key frames and motion paths. Using this class enables you to perform the big three in x, y and z: movement, rotation, and scale. The code below demonstrates the use of the
516
Chapter 13: Making 3D Movies AnimateTransform class to translate a logo in x, y, z, rotate it in y, and scale it in x. It’s similar to the jet animation project presented earlier where you play back property and time values, as opposed to state and time values, in order to create the animation sequence:
(continued)
517
Part IV: Extending PV3D and Beyond (continued)
Using such state-animation engines to play back recorded events can be a powerful tool in education. In addition to hand-coding the spark animation above, Flash Catalyst creates such animation through graphical techniques. We demonstrate this in upcoming chapters. Closely related to the idea of state engines, saving XML to the web, and creating 3D environments, is PV3D studio, created by Gary Stasiuk.
Building a PV3D Studio Every PV3D developer ’s dream is to create a Studio Max-like website builder for Papervision3D. Such an application would let you manipulate 3D objects, place textures on them, and assign interactivity. Upon completion of your project, a single click would upload it to the web without even writing a single line of code. Though this may sound a little farfetched, Gary Stasiuk of Juxt Interactive has made a worthy attempt with his creation of VizualPV3D. You can download VizualPV3D at http://www.juxtinteractive .com/work/vizualpv3d/.
VizualPV3D VizualPV3D has a Flash GUI interface that makes creating and manipulating objects in 3D scenes easy to do (see Figure 13.18).
518
Chapter 13: Making 3D Movies
Figure 13-18 The big deal is that session data is exported as XML and sent to your clip board. You can then save this data as XML and open up your session again later. And if you port this program over to Flash 10 you can save this data directly to your hard drive. This idea of creating web-ready interfaces from graphical UIs is exactly what Flash Catalyst is doing. The next advent of 3D technology will be one that takes an interface like’s Gary’s and reprograms it to interact with Flash Builder in the same way that Flash Catalyst does.
Summar y This chapter highlights one of the greatest challenges developers face today – the rapid change in programming architecture and platforms. With such fast-paced changes, literally once an application is written it’s obsolete. This was demonstrated by building the Seven Revolutions project from the previous chapter in Flash CS4. Next, Flash Catalyst and its integration into Flash Builder were examined. Finally, the new Animate Super Effects class was used to create a panel animation, Photoshop3D was examined, and a PV3D animation engine which saves its results to the DOM developed. Keeping up with technology changes and actively incorporating them was the keynote of this chapter as you examined Flash Catalyst and Flash Builder, Adobe’s two newest products.
519
Taking Vir tual Tours In the 1980s, military doctrine was pretty simple. Whoever controlled the next medium controlled the world and that next medium was Space. Space was the last of the four great media (land, sea, air, and space). And that was all there was, but since the 1980s a number of new global factors have come into play, namely those mentioned in the Seven Revolutions of Chapter 12. And in this new millennium two new media will dominate the world stage: nanotechnology and virtual reality. As our technology crazed world moves forward, the benefits and threats of new technologies emerge. World planners wrestle with where to invest their resources in order to gain the greatest advantage for society. With technology changing so rapidly, programming interfaces must be built that generate code at a blazing pace to keep up. With the advent of Adobe’s new Flash Catalyst and Flash Builder, code is now being generated automatically – and that’s a huge leap forward. In this chapter, you take a brief look at how the military is using virtual technology, augmented reality, and building 3D worlds.
Hello “Uncle Sam ” Applying virtual reality to military development has two sides: the yin and the yang. The yin part is the soft side of “military-virtual” reality and it looks really good to policy makers: acceleration of development schedules and fewer people getting killed in deployment testing. The yang part is the hard side of “military-virtual” reality and it sounds really good to the public: bloodless battle, and economic stimulus for communities that are building the next level of virtually enhanced warfare.
Part IV: Extending PV3D and Beyond Embracing the Yin of VR The U.S. military is embracing VR in a number of areas such as training, weaponry development, field command, droid deployment, and control platforms. And here are some of the reasons why: ❑
VR offers a factor of 20 in savings. The military doesn’t have to spend a million dollars when an easy entry VR system can be purchased for US$50,000.
❑
Battles are staged with VR simulators, which are becoming more realistic with each new iteration of the technology. It’s a military fact that soldiers in the heat of battle perform by rote what they’ve been trained to do, and VR simulators are successfully training them.
❑
Conceptual designs are more easily tested using VR, eliminating the need to build physical prototypes and ship them overseas to see how they perform in realistic battle terrain.
❑
Early feedback eliminates lethal design flaws closer to production, making design phase 1 more productive and less politically driven. Such feedback could also potentially eliminate dead systems, accelerating the prototype to deployment by a number of years, and making such systems safer for soldiers to operate.
❑
Bench marking military prototypes by VR technology will eliminate wasteful congressional spending: where emotional and economic factors have in the past played more of a role in weaponry funding than potential weapon effectiveness.
❑
Road mapping of military technologies becomes more effective, as the result of weapon technology enhancements can be demonstrated without actually building prototypes. And it reduces the time it takes to integrate new technologies by a number of years.
Not only does VR shorten development time of advanced weaponry, it also performs a significant role in a number of other areas such as training exercises.
Using Drones – the Yang of VR The U.S. military has envisioned systems from as simple as UAVs (unmanned aerial vehicles) dropping pinpoint accurate missiles, to entire droid armies being controlled by space satellites from remote locations using virtual reality. Figure 14.1 shows the predator drone loaded with a Hellfire missile.
Figure 14-1
522
Chapter 14: Taking Virtual Tours As drones become more widely used in warfare it will become apparent that VR will become an important military medium in playing a decisive role in global conflicts of the future: conflicts that’ll be seen as bloodless by the side that’s controlling the VR medium. The next topic was a delightful mind blower for many PV3D developers – augmented reality.
Creating Augmented Reality Generating augmented reality in PV3D is done using the FLARToolKit . The FLARToolKit was created from the Java version of NyARToolKit, which was created from the C version of ARToolKit. So what’s augmented reality anyway? It’s the overlay of virtual imagery on the real world. As shown in Figure 14.2, the creator of FLARToolKit, Japanese coder Saqoosha (at http://saqoosha.net/en/), has a two-level boxed hole in his head with firework-like graphics shooting out of it.
Figure 14-2 Real-time blending of real-world footage and computer-generated graphics blurs the line between reality and the virtual world by enhancing what you perceive. It’s a field that’s wide open for developers and will amaze your clients every time they view one of your FLARToolkit applications. Here are the steps for getting started with the FLARToolKit in PV3D: ❑
Download the FLARToolKit starter file from http://saqoosha.net/en/flartoolkit/ start-up-guide/ and unzip it. This folder contains everything you need to create your first FLART experiment.
❑
Or better yet, to keep your work up to date you can download the starter file from the SVN address at http://www.libspark.org/svn/ (just surf down to the appropriate folder).
❑
After unzipping the starter file, print a copy of the flarlogo-marker pdf found in the Data folder. The flarlogo is shown on the left side of figure 14.3. This FLAR (Flash Augmented Reality) application throws a 3D cube on top of this pattern when you run the program.
❑
Make sure that your computer ’s web cam is hooked up and active.
❑
Finally, open up the SimpleCube fla in Flash and run the program. Your webcam then becomes active, and when you put your flarlogo-marker pattern in front of it a PV3D cube appears on top of the pattern as shown on the right side of Figure 14.3.
523
Part IV: Extending PV3D and Beyond
Figure 14-3 Basically, you create your FLAR scene just as you would any other PV3D application except with one big difference: PV3D uses an augmented left-handed coordinate system and FLARToolKit uses a right-handed coordinate system as shown in Figure 14.4.
Figure 14-4
The code required to render the cube in Figure 14.3 (on the right side) is rather simple. After the PV3D imports are declared, a Simple Cube class is created which extends the PV3DARApp (PV3D augmented reality) class. Then two primitives are declared: a plane and a cube. The plane is a red wire frame and overlays the FLART pattern. This gives an indication to the user that the program is working. The cube is the intended object that sits on top of your pattern. Next the FLARToolKit needs to be initiated. This is done using the following line of code. this.init(‘Data/camera_para.dat’, ‘Data/flarlogo.pat’);
The first argument is the path for the camera corrective file. You can leave it as default. The second argument is a definition file for your marker pattern. You need to define the pattern for the marker (for
524
Chapter 14: Taking Virtual Tours example, flarlogo.pat is for flarlogo-marker.pdf). Later in this chapter you learn how to make your own markers. Once the init() method is completed, the onInit() method is called. This method sets up your wireframe, point light source, cube material, and cube. It then adds them to your baseNode. Using the following code, you add your wireframe and cube to the FLAR pattern using _baseNode as opposed to adding your objects to your scene, which is typically done in PV3D. This line adds your wireframe to FLAR: this._baseNode.addChild(this._plane);
This line adds your cube to FLAR: this._baseNode.addChild(this._cube);
The _baseNode container is a special object that follows the marker automatically. The complete documented code is given here: package { //Papervision3D Imports import org.papervision3d.lights.PointLight3D; import org.papervision3d.materials.WireframeMaterial; import org.papervision3d.materials.shadematerials.FlatShadeMaterial; import org.papervision3d.materials.utils.MaterialsList; import org.papervision3d.objects.primitives.Cube; import org.papervision3d.objects.primitives.Plane; //Public class extends the PV3D Augmented Reality Class public class SimpleCube extends PV3DARApp { //Declare plane and cube properties private var _plane:Plane; //Wireframe Plane private var _cube:Cube; //Projected Cube public function SimpleCube() { /* Initialize FLARToolKit. The 1st argument is a path for the camera corrective file. You can leave it as default. The 2nd argument is a definition file for your marker pattern. You need to define the pattern for the marker. e.g. flarlogo.pat is for flarlogo-marker.pdf. */ this.init(‘Data/camera_para.dat’, ‘Data/flarlogo.pat’); }
/* Once init call is successfully over, this function is called. You can adjust 3D objects here. If you can’t call this function and an error message comes out, you should check the availability of pattern-file OR condition of your Webcam. */
(continued)
525
Part IV: Extending PV3D and Beyond (continued) protected override function onInit():void { super.onInit(); /* This code discriminates if the marker works fine or not. They publish the plane as an overlay of the marker pattern, as the same size of the marker. */ var wmat:WireframeMaterial = new WireframeMaterial(0xff0000, 1, 2); this._plane = new Plane(wmat, 80, 80); // 80mm x 80mm /* Papervision3D and FLARToolKit have an opposite coordinate system. This code adjusts the PV3D coordinate system. */ this._plane.rotationX = 180; //Adds dummy wireframe plane used to see if FLARTooKit is working this._baseNode.addChild(this._plane); //Instantiates the PV3D light source for your material var light:PointLight3D = new PointLight3D(); light.x = 0; light.y = 1000; light.z = -1000; // Instantiates your cube and creates its materials var fmat:FlatShadeMaterial = new FlatShadeMaterial(light, 0xff22aa, 0x75104e); this._cube = new Cube(new MaterialsList({all: fmat}), 40, 40, 40); this._cube.z = 20; //Add the cube to the baseNode /* Papervision3D normally uses addChild to add an object to a Scene3D, FLARToolKit needs to use addChild to add an object to a special node which controls the 3D object following the marker. The special node is _baseNode. Once you use addChild to add your object to _baseNode, the object follows the marker automatically. */ this._baseNode.addChild(this._cube); }}}
Now that you’ve gone through Saqoosha’s basic tutorial, it’s time to do something a little more elaborate. Such as play a game in augmented reality.
526
Chapter 14: Taking Virtual Tours
Playing a Game in Augmented Reality Before you begin putting your game into augmented reality there’s a little work to be done to Saqoosha’s example code above. In its present form, it’s not very useful as starter code. You need to put it into BasicView format so you can use it for any PV3D application more easily. In the following section, you fix it up so you can use it to start any PV3D project.
Creating BasicView Starter Code Transforming Saqoosha’s code into PV3D BasicView starter code takes a little work. Essentially, five basic methods are involved: ❑
setupFlar — this method takes your marker and camera correction files and creates the Flar camera. It also creates your Flar node and adds it to your PV3D scene.
❑
setupWebCam — this method creates your camera and camera display object and attaches your camera to that display object. It then attaches your camera to the viewport and sets its mode.
❑
setupBitmap — this method is the heart of the Flar process. On every frame you take a bitmap data shot of the webcam and pass it to the Flar tool kit and the Flar tool kit detects your pattern in that data.
❑
setupPV3D — this method sets up your PV3D objects. It also places those objects in a display object class which is then placed in the Flar node.
❑
startRendering — this method captures your webcam data in a bitmapData object and sends it to your Flar detector which finds its pattern. It then gets the transformation data from the FLARToolKit and applies it to your 3D scene.
Sorting all this out takes a little work, but it’s not any more complicated than anything you’ve already done in this book. Essentially, you integrate the FLARToolKit into PV3D using the methods above. The rest is just good old PV3D, and you can add animation to your objects as shown in Figure 14.5 (where the cube is both rotating and expanding).
Figure 14-5
527
Part IV: Extending PV3D and Beyond As shown in the code snippet here, everything is PV3D as usual once you integrate the FLARToolKit using the methods discussed previously. However, there’s a big difference in the render method. The PV3D camera is replaced by the Flar camera. So what you see comes from the eyes of Flar. And that’s pretty cool! //Add a little Animation to your Cube oscParam+=4; oscParam%=360; this._cube.rotationZ+=4; this._cube.rotationY+=3; this._cube.scaleZ=2+Math.sin(oscParam*Math.PI/180); this._cube.scaleY=this._cube.scaleX=2-Math.sin(oscParam*Math.PI/180); //Render your scene renderer.renderScene(scene, flarCam3D, viewport);
The following code has been extensively documented and is meant as starter code for any typical BasicView Flar project in PV3D: package { //Flash Imports import flash.display.Bitmap; import flash.display.BitmapData; import flash.events.Event; import flash.media.Camera; import flash.media.Video; import flash.utils.ByteArray; //FLAR import import import import import import import
Imports org.libspark.flartoolkit.core.FLARCode; org.libspark.flartoolkit.core.param.FLARParam; org.libspark.flartoolkit.core.raster.rgb.FLARRgbRaster_BitmapData; org.libspark.flartoolkit.core.transmat.FLARTransMatResult; org.libspark.flartoolkit.detector.FLARSingleMarkerDetector; org.libspark.flartoolkit.pv3d.FLARBaseNode; org.libspark.flartoolkit.pv3d.FLARCamera3D;
//PV3D import import import import import import import import
Imports org.papervision3d.lights.PointLight3D; org.papervision3d.materials.BitmapMaterial; org.papervision3d.materials.WireframeMaterial; org.papervision3d.materials.shadematerials.FlatShadeMaterial; org.papervision3d.materials.utils.MaterialsList; org.papervision3d.objects.DisplayObject3D; org.papervision3d.objects.primitives.*; org.papervision3d.view.BasicView;
//Create your SWF metadata [SWF(width=”640”, height=”480”, backgroundColor=”#000000”, frameRate=”30”)] public class BasicViewFLAR extends BasicView { //Embed your Marker and Camera correction data [Embed(source=”assets/data/camera_para.dat”, mimeType=”application/octet-stream”)]
528
Chapter 14: Taking Virtual Tours private var CamParams:Class; //Marker that your FLARToolKit will detect [Embed(source=”assets/data/flarlogo.pat”, mimeType=”application/octet-stream”)] private var MarkerPat:Class; //Create a display object to hold your PV3D objects private var myDisplayObject:DisplayObject3D= new DisplayObject3D(); //Camera properties private static const WIDTH:Number = 640; private static const HEIGHT:Number = 480; private static const FRAMES_PER_SECOND:int = 60; private private private private
var var var var
camParams:FLARParam; markerPat:FLARCode; raster:FLARRgbRaster_BitmapData; detector:FLARSingleMarkerDetector;
private var webCam:Camera; private var camDisplay:Video; private var capture:BitmapData; //FLAR properties private var flarCam3D:FLARCamera3D; private var flarNode:FLARBaseNode; private var transResult:FLARTransMatResult; private var oscParam:Number=0; //PV3D primitives private var _plane:Plane; private var _cube:Cube; //Start up BasicView and initiate your program public function BasicViewFLAR():void { super(WIDTH * 2, HEIGHT * 2, false); init(); } //Run your initiation functions private function init():void { setupFlar(); setupWebCam(); /*Set Up Bitmap is the heart of the Flar process. On every frame you take a bitmap data shot of the webcam and pass it to the FLARToolKit and the FLARToolKit detects your pattern in that data.*/ setupBitmap(); setupPV3D(); startRendering(); } //Set up FLAR private function setupFlar():void { camParams = new FLARParam(); camParams.loadARParam(new CamParams() as ByteArray); markerPat = new FLARCode(16, 16);
(continued)
529
Part IV: Extending PV3D and Beyond (continued) markerPat.loadARPatt(new MarkerPat()); //Instantiate your FLAR camera and node flarCam3D = new FLARCamera3D(camParams); flarNode = new FLARBaseNode(); //Add your FLAR node to your PV3D scene scene.addChild(flarNode); transResult = new FLARTransMatResult(); } //Set up your Camera private function setupWebCam():void { //Instantiate your camera camDisplay = new Video(); camDisplay.width = WIDTH; camDisplay.height = HEIGHT; webCam = Camera.getCamera(); //Attach Camera camDisplay.attachCamera(webCam); //Add video component to the stage addChildAt(camDisplay, getChildIndex(viewport)); webCam.setMode(WIDTH / 2, HEIGHT / 2, FRAMES_PER_SECOND); } //Capture your Camera scene private function setupBitmap():void { capture = new BitmapData(camDisplay.width, camDisplay.height, false, 0x0); //Draw video object that contains your webcam capture.draw(camDisplay); //Two import FLAR objects that will let you detect your pattern raster = new FLARRgbRaster_BitmapData(capture); //Detect where the pattern is found detector = new FLARSingleMarkerDetector(camParams, markerPat, 80); } //Create your PV3D objects private function setupPV3D():void { //Viewport fix from Saqoosha due to camera corrections viewport.x = -4; //Create your wireframe plane that will overlay your pattern var wmat:WireframeMaterial = new WireframeMaterial(0xff0000, 1, 2); this._plane = new Plane(wmat, 80, 80); // 80mm x 80mm //Instantiate the PV3D light source for your material var light:PointLight3D = new PointLight3D(); light.x = 0; light.y = 1000; light.z = -1000; // Instantiate your cube and create its materials var fmat:FlatShadeMaterial = new FlatShadeMaterial(light, 0xff22aa, 0x75104e);
530
Chapter 14: Taking Virtual Tours this._cube = new Cube(new MaterialsList({all: fmat}), 40, 40, 40); this._cube.z = 20; //Add your cube and plane to your display object myDisplayObject.addChild(_plane); myDisplayObject.addChild(_cube); //Rotate your plane into position this._plane.rotationX = 180; //Add your displayobject to your FLAR Node flarNode.addChild(myDisplayObject); } //Start rendering your PV3D scene override protected function onRenderTick(event:Event = null):void { //Capture bitmap data from the webcam it’s now stored inside of bitmapdata capture.draw(camDisplay); //Add a try-catch to keep errors from shutting you down try { //Pass in the raster object which has the bitmapdata and passes it to FLAR if(detector.detectMarkerLite(raster, 80) && detector.getConfidence() > 0.5) { //Get the transformation from the FLARToolKit and apply it to the 3D scene detector.getTransformMatrix(transResult); flarNode.setTransformMatrix(transResult); flarNode.visible = true; }else { flarNode.visible = false; }} catch(e:Error){trace(“Catch a falling error and put it in your pocket!”);} //Add a little Animation to your Cube oscParam+=4; oscParam%=360; this._cube.rotationZ+=4; this._cube.rotationY+=3; this._cube.scaleZ=2+Math.sin(oscParam*Math.PI/180); this._cube.scaleY=this._cube.scaleX=2-Math.sin(oscParam*Math.PI/180); //Render your scene renderer.renderScene(scene, flarCam3D, viewport); }}}
The marker used for this project (shown in Figure 14.3) is pretty boring and most likely you’ll want to do some corporate branding for your FLAR projects.
Generating Your Own Markers When you open up a marker file you see four sets of 16 48 matrices, which is your marker from four different directions. Your marker is seen by FLAR as 16 16 barcode. The reason the matrix is 16 48 is that you need to triple its size to account for each RGB color: that’s 16 16 three times or 16 48. Luckily there have been a few marker generators released on the web that help you create your own markers automatically.
531
Part IV: Extending PV3D and Beyond Tarotaro has a great marker maker on their blog at http://flash.tarotaro.org/blog/2008/ 12/14/artoolkit-marker-generator-online-released/.
It runs online so there is no AIR installation (which means you need the Flash 10 player to save the marker file to your hard drive). The application gives you a preview of your marker, lets you choose marker mode or load a marker image from your hard drive, has a “get pattern” button, and lets you adjust your marker segments and sides as shown in Figure 14.6.
Figure 14-6 Here’s how you use it:
1. 2. 3. 4. 5. 6. 7.
Design your original marker and print it. Open ARToolKit Marker Generator Online. Set segments and marker size. Point your webcam at the printed marker, or load a file generated in Photoshop (for example). Push Get Pattern button when a red line encloses the marker. Preview your marker image. Push Save button to save your pattern file *.pat. You must add the .pat extension to your file.
Here are two important tips: When you use this program you must make sure that you add the extension .pat to your file when saving it. Flash 10 doesn’t add the file extension automatically when you save. Also, when loading an image file make sure that you have your large black rectangle surrounded by white, as shown in Figure 14.6, or you won’t get a correct marker capture.
532
Chapter 14: Taking Virtual Tours In addition to using the online version, you can obtain the marker generator source code from the SVN given earlier and create your own generator. The code is located in the a3/ARToollKitMarkerGenerator folder. You now add Jiglib physics to FLAR – thanks to polymorphism!
Adding Jiglib Physics to FLAR Employing the starter code above makes it really easy to add Jiglib physics to FLAR. All you have to do is place everything into your display object (myDisplayObject), which is then placed into your FLAR Node (flarNode) object. As an example of how this works, you add the Jiglib Hello World example of Chapter 9 to FLAR. Here’s what you do:
1.
Create a new project with your FLAR starter code and open up the Jiglib Hello World example from Chapter 9 as a reference. You’ll be transferring code from this app to your FLAR starter code.
2. 3.
Transfer the Jiglib folder so you have all the physics classes in your starter code.
4.
Add interactivity to your super method by changing its third augment to true.
Transfer all the imports, embed statement(s), and properties from the Jiglib Hello World to your FLAR starter code. Go ahead and test your program to see if there are any errors. Every time you make a change to Flash or Flex you should test it to make sure that you haven’t introduced an error. Making five or six changes and then testing may leave you wondering which change created the error.
super(WIDTH * 2, HEIGHT * 2, true, false);
5.
Add all the appropriate function calls, key listeners, point light(s), and the physics PV3D plugin to your constructor function as shown in the following code snippet:
public function JiglibFLAR():void { super(WIDTH * 2, HEIGHT * 2, true, false); //KeyListeners stage.addEventListener( KeyboardEvent.KEY_DOWN, myKeyDown ); stage.addEventListener( KeyboardEvent.KEY_UP, myKeyUp ); //Set Light sceneLight = new PointLight3D(true, true); sceneLight.x = 10; sceneLight.y = 300; sceneLight.z = -400; // Initialize the Papervision3D physics plugin physics = new Papervision3DPhysics(myDisplayObject, 7); //Initiate Listeners createFloor(); createHelloSphere(); init(); }
533
Part IV: Extending PV3D and Beyond And here’s the big trick! In the Papervision3DPhysics method, replace the scene with myDisplayObject. This puts all your Jiglib objects into your display object. Then the display object is placed into your flarNode, which is placed into your scene. physics = new Papervision3DPhysics(myDisplayObject, 7);
In seeing how easy this is, you should really gain an appreciation of how powerful polymorphism truly is.
6.
Now place all the appropriate methods from your Jiglib code into your starter code (key methods, create floor method, and createHelloSphere method).
7.
Finally, everywhere you see scene.addChild in your code, change it to myDisplayObject. addChild. That’s all there is to it. When you run the program and place your marker in front of your webcam you now have the key controlled Jiglib Hello World in FLAR as shown in Figure 14.7.
Figure 14-7 The entire source code is as follows: package { //Flash Imports import flash.display.Bitmap; import flash.display.BitmapData; import flash.events.Event; import flash.media.Camera; import flash.media.Video;
534
Chapter 14: Taking Virtual Tours import flash.utils.ByteArray; import flash.events.KeyboardEvent; import flash.ui.Keyboard; //Jiglib Flash import jiglib.geometry.JSphere; import jiglib.math.JNumber3D; import jiglib.physics.RigidBody; import jiglib.plugin.papervision3d.Papervision3DPhysics; import jiglib.plugin.papervision3d.Pv3dMesh; //FLAR import import import import import import import
Imports org.libspark.flartoolkit.core.FLARCode; org.libspark.flartoolkit.core.param.FLARParam; org.libspark.flartoolkit.core.raster.rgb.FLARRgbRaster_BitmapData; org.libspark.flartoolkit.core.transmat.FLARTransMatResult; org.libspark.flartoolkit.detector.FLARSingleMarkerDetector; org.libspark.flartoolkit.pv3d.FLARBaseNode; org.libspark.flartoolkit.pv3d.FLARCamera3D;
//PV3D import import import import import import import import import import import import
Imports org.papervision3d.lights.PointLight3D; org.papervision3d.materials.BitmapMaterial; org.papervision3d.materials.WireframeMaterial; org.papervision3d.materials.shadematerials.FlatShadeMaterial; org.papervision3d.materials.utils.MaterialsList; org.papervision3d.objects.DisplayObject3D; org.papervision3d.objects.primitives.*; org.papervision3d.cameras.CameraType; org.papervision3d.cameras.SpringCamera3D; org.papervision3d.core.math.Number3D; org.papervision3d.view.layer.util.ViewportLayerSortMode; org.papervision3d.view.BasicView;
//Create your SWF metadata [SWF(width=”640”, height=”480”, backgroundColor=”#000000”, frameRate=”30”)] public class JiglibFLAR extends BasicView { [Embed(source=”assets/helloearth.jpg”)] public var EarthTexture:Class; //Variable Declaration private var physics:Papervision3DPhysics; private var sphereObject:Sphere; private var helloSphere:RigidBody; private var moveForce:Number = 6; private var springCamera:SpringCamera3D; private var cameraTarget:DisplayObject3D; private var sceneLight:PointLight3D;
(continued)
535
Part IV: Extending PV3D and Beyond (continued) //Key Listeners private var keyRight:Boolean = false; private var keyLeft:Boolean = false; private var keyForward:Boolean = false; private var keyReverse:Boolean = false; private var keyUp:Boolean = false; //Embed your Marker and Camera correction data [Embed(source=”assets/data/camera_para.dat”, mimeType=”application/octet-stream”)] private var CamParams:Class; //Marker that your FLARToolKit will detect [Embed(source=”assets/data/flarlogo.pat”, mimeType=”application/octet-stream”)] private var MarkerPat:Class; //Create a display object to hold your PV3D objects private var myDisplayObject:DisplayObject3D= new DisplayObject3D(); //Camera properties private static const WIDTH:Number = 640; private static const HEIGHT:Number = 480; private static const FRAMES_PER_SECOND:int = 60; private private private private
var var var var
camParams:FLARParam; markerPat:FLARCode; raster:FLARRgbRaster_BitmapData; detector:FLARSingleMarkerDetector;
private var webCam:Camera; private var camDisplay:Video; private var capture:BitmapData; //FLAR properties private var flarCam3D:FLARCamera3D; private var flarNode:FLARBaseNode; private var transResult:FLARTransMatResult; private var oscParam:Number=0; //PV3D primitives private var _plane:Plane; private var _cube:Cube; //Start up BasicView and initiate your program public function JiglibFLAR():void { super(WIDTH * 2, HEIGHT * 2, true, false); //KeyListeners stage.addEventListener( KeyboardEvent.KEY_DOWN, myKeyDown ); stage.addEventListener( KeyboardEvent.KEY_UP, myKeyUp ); //Set Light sceneLight = new PointLight3D(true, true); sceneLight.x = 10; sceneLight.y = 300; sceneLight.z = -400; // Initialize the Papervision3D physics plugin physics = new Papervision3DPhysics(myDisplayObject, 7);
536
Chapter 14: Taking Virtual Tours //Initiate Listeners createFloor(); createHelloSphere(); init(); } //Create your floor private function createFloor():void { var myFloor:Plane = new Plane(new WireframeMaterial(0xFFFFFF), 10000, 10000, 10000*0.001, 10000*0.001); myFloor.rotationX = 90; myFloor.y = -150; myDisplayObject.addChild(myFloor); //Generate ground from PV3D Plugin physics.createGround(new WireframeMaterial(0xFFFFFF, 0), 1800, 0); } //Create Hello World Sphere private function createHelloSphere():void { //Define Material for Hello World Sphere var helloWorld:BitmapMaterial = new BitmapMaterial(Bitmap(new EarthTexture()). bitmapData, true); helloWorld.smooth = true; sphereObject = new Sphere(helloWorld, 120, 12, 12); myDisplayObject.addChild(sphereObject); //Instantiate Jiglib sphere and attach PV3D Sphere helloSphere = new JSphere(new Pv3dMesh(sphereObject), 100); helloSphere.y = 180; helloSphere.rotationY+=60; helloSphere.restitution = 3; helloSphere.mass = 1 physics.addBody(helloSphere); //Define DisplayObject3D cameraTarget cameraTarget = new DisplayObject3D(); cameraTarget.copyPosition(sphereObject); myDisplayObject.addChild(cameraTarget); } //Key Handler Methods //myKeyUp Method private function myKeyUp(event:KeyboardEvent):void { switch(event.keyCode) {
(continued)
537
Part IV: Extending PV3D and Beyond (continued) case Keyboard.UP: keyForward = false; break; case Keyboard.DOWN: keyReverse = false; break; case Keyboard.LEFT: keyLeft = false; break; case Keyboard.RIGHT: keyRight = false; break; case Keyboard.SPACE: keyUp=false; }} //myKeyDown Method private function myKeyDown(event:KeyboardEvent):void { switch(event.keyCode) { case Keyboard.UP: keyForward = true; keyReverse = false; break; case Keyboard.DOWN: keyReverse = true; keyForward = false; break; case Keyboard.LEFT: keyLeft = true; keyRight = false; break; case Keyboard.RIGHT: keyRight = true; keyLeft = false; break; case Keyboard.SPACE: keyUp = true; break; }} //Run your initiation functions private function init():void {
538
Chapter 14: Taking Virtual Tours setupFlar(); setupWebCam(); //Set Up Bitmap is the heart of the FLAR process. setupBitmap(); setupPV3D(); startRendering(); } //Set up FLAR private function setupFlar():void { camParams = new FLARParam(); camParams.loadARParam(new CamParams() as ByteArray); markerPat = new FLARCode(16, 16); markerPat.loadARPatt(new MarkerPat()); //Instantiate your FLAR camera and node flarCam3D = new FLARCamera3D(camParams); flarNode = new FLARBaseNode(); //Add your FLAR node to your PV3D scene scene.addChild(flarNode); transResult = new FLARTransMatResult(); } //Set up your Camera private function setupWebCam():void { //Instantiate your camera camDisplay = new Video(); camDisplay.width = WIDTH; camDisplay.height = HEIGHT; webCam = Camera.getCamera(); //Attach Camera camDisplay.attachCamera(webCam); //Add video component to the stage addChildAt(camDisplay, getChildIndex(viewport)); webCam.setMode(WIDTH / 2, HEIGHT / 2, FRAMES_PER_SECOND); } //Capture your Camera scene private function setupBitmap():void { capture = new BitmapData(camDisplay.width, camDisplay.height, false, 0x0); //Draw video object that contains your webcam capture.draw(camDisplay); //Import FLAR objects that will let you detect your pattern //Pass in the Camera Data raster = new FLARRgbRaster_BitmapData(capture); //Detect whether the pattern is found detector = new FLARSingleMarkerDetector(camParams, markerPat, 80); } //Create your PV3D objects private function setupPV3D():void {
(continued)
539
Part IV: Extending PV3D and Beyond (continued) //Viewport fix from Saqoosha due to Camera corrections viewport.x =100; //Add your displayobject to your FLAR Node this.myDisplayObject.rotationX=90; flarNode.addChild(myDisplayObject); myDisplayObject.z=-500; } //Start rendering your PV3D scene override protected function onRenderTick(event:Event = null):void { //Capture bitmap data from the webcam, it’s now stored inside of bitmapdata capture.draw(camDisplay); //Add a try-catch to keep errors from shutting you down try {//pass in raster object which has the bitmapdata and passes it to FLAR if(detector.detectMarkerLite(raster, 80) && detector.getConfidence() > 0.5) { //Get transformation from FLARToolKit and apply (set) it to the 3D scene detector.getTransformMatrix(transResult); flarNode.setTransformMatrix(transResult); flarNode.visible = true; }else { flarNode.visible = false; }} catch(e:Error){trace(“Catch a falling error and put it in your pocket!”);} //Jiglib Code if(keyLeft) { helloSphere.addWorldForce(new JNumber3D(0, 0, moveForce), helloSphere.currentState.position); } if(keyRight) { helloSphere.addWorldForce(new JNumber3D(0, 0,-moveForce), helloSphere.currentState.position); } if(keyForward) { helloSphere.addWorldForce(new JNumber3D(moveForce,0,0 ), helloSphere.currentState.position); } if(keyReverse) {
540
Chapter 14: Taking Virtual Tours helloSphere.addWorldForce(new JNumber3D(-moveForce, 0, 0 ), helloSphere.currentState.position); } if(keyUp) { helloSphere.addWorldForce(new JNumber3D(0, 2*moveForce, 0), helloSphere.currentState.position); } //Execute rendering process physics.step(); //myDisplayObject.copyPosition(helloSphere);
//Render your scene renderer.renderScene(scene, flarCam3D, viewport); }}}
The orientation of your pattern influences the direction of your ball directional keys. For example, if you rotate your marker by 180 degrees your keys work in an opposite way to how they did before. If you’re bored with just one marker at a time, don’t fret … you can have multiple markers. And the place to go to find out more (besides the book’s website) is www.squidder.com.
Creating Multiple Markers Generating multi-FLAR markers is similar to creating a particle system. You extend the single marker FLAR class, create an array of markers, and iterate over that array each iteration of your scene. The results are shown in Figure 14.8.
Figure 14-8
541
Part IV: Extending PV3D and Beyond The code snippet that generates the results array for the multiple markers is as follows: override protected function _detectMarkers(): void { _resultsArray = _flarDetector.updateMarkerPosition( _flarRaster, 80, .5 ); for ( var i: int = 0; i < _resultsArray.length; i ++ ) { var subResults: Array = _resultsArray[ i ]; for ( var j: * in subResults ) { _flarDetector.getTransmationMatrix( subResults[ j ], _resultMat ); if ( _cubes[ i ][ j ] != null ) transformMatrix( _cubes[ i ][ j ], _resultMat ); }}
You can download the complete source code and read a brief explanation of its creation from http:// www.squidder.com/2009/03/06/flar-how-to-multiple-instances-of-multiple-markers/.
Well – after all of that, you’re finally ready to add a game to FLAR.
Adding Pong to FLAR Putting Pong into FLAR is accomplished in the same way that you put the Jiglib Hello World above into FLAR. Start by opening up the Pong app from Chapter 10 on games and the FLAR Jiglib Hello World app above simultaneously in Flex. This way you can copy and paste the needed methods from one app to the other. Then go through the following steps:
1. 2.
Rename the FLAR Jiglig app to PongFLAR. Transfer the Lively folder that contains the Skybox and Arena classes and place the appropriate import statements in the PongFLAR code.
//Lively Imports import org.lively3d.jiglib.Arena; import org.lively3d.jiglib.Skybox;
3.
Transfer the Embed assets files and code.
//Sound [Embed(source=”assets/sound/dink.mp3”)] public var hitSoundN:Class; [Embed(source=”assets/sound/collision.mp3”)] public var scoreSound:Class; [Embed(source=”assets/sound/win.mp3”)] public var wonSound:Class; [Embed(source=”assets/sound/gameOver.mp3”)] public var lostSound:Class;
4.
Add the appropriate Pong game properties.
//Pong public public public public
542
Game var score1:int=0; var score2:int=0; var arena: Arena; var yaw: Number = 0;
Chapter 14: Taking Virtual Tours public var targetYaw: Number = 0; public var pitch: Number = 0; public var targetPitch: Number = 0; public var isMouseDown: Boolean = false; public var lastMousePoint: Point;
public const SCREEN_WIDTH: int = 800; public const SCREEN_HEIGHT: int = 500;
public const GAME_WIDTH:int = 600; public const GAME_HEIGHT:int = 400; private var mySkybox:Skybox = new Skybox(); private var mylight:PointLight3D; private private private private private private private private private
var var var var var var var var var
myHeight :Number=300; myWidth :Number=600; myDepth :Number=400; ballBody:RigidBody; bar1:RigidBody; bar2:RigidBody; scorePlayer1:int=0; scorePlayer2:int=0; hitOnce:Boolean=true;
private var shadeMateria:FlatShadeMaterial; private var vplObjects:ViewportLayer; private private private private private private
var var var var var var
materiaList:MaterialsList; checkSpeed:Timer; oldX1:Number=0; oldX2:Number=0; myNum1:int=0; myNum2:int=0;
private var targetX:Number; private var targetY:Number; private var easing:Number=7; private private private private private
5.
var var var var var
myTextField:TextField; hisTextField:TextField; myMessage:TextField; levelInt:int=1; ballSpeedIs:Number=60;
Add the method required to initiate all the PV3D game objects. Change the scene.addChild method to myDisplayObject.addChild and make sure that the PV3D physics objects reference the myDisplayObject as opposed to the scene object.
543
Part IV: Extending PV3D and Beyond private function initObjects():void { // Create all the game objects arena = new Arena(myWidth, myDepth); //Change from scene.addChilde to myDisplayObject.addChild myDisplayObject.addChild(arena); //Change the scene reference to myDisplayObject physics = new Papervision3DPhysics(myDisplayObject, 8); shadeMateria = new FlatShadeMaterial(mylight, 0x77ee77); var myWire:WireframeMaterial=new WireframeMaterial(0x000000); myWire.doubleSided=true; physics.createPongBoundingBox(myWire, myWidth, myDepth, myHeight,0,-10,0); vplObjects = new ViewportLayer(viewport,null); vplObjects.layerIndex = 2; vplObjects.sortMode = ViewportLayerSortMode.Z_SORT; viewport.containerSprite.addLayer(vplObjects); shadeMateria = new FlatShadeMaterial(mylight, 0xff6666); //Create the ball shadeMateria.interactive = true; ballBody = physics.createSphere(shadeMateria, 16, 12, 12); ballBody.mass = 1; ballBody.friction=.2; ballBody.restitution=.8; ballBody.moveTo(new JNumber3D( -100, 0, -100)); vplObjects.addDisplayObject3D(physics.getMesh(ballBody)); shadeMateria = new FlatShadeMaterial(mylight, 0xffff44); materiaList = new MaterialsList(); materiaList.addMaterial(shadeMateria,”all”); //Create the deflection bars bar1 = physics.createCube(materiaList, 16, 86, 32); bar1.moveTo(new JNumber3D(-280, 0, 0)); bar1.movable=false; bar1.restitution=.8; bar1.friction=.2; vplObjects.addDisplayObject3D(physics.getMesh(bar1)); bar2 = physics.createCube(materiaList, 16, 86, 32); bar2.moveTo(new JNumber3D(280, 0, 0)); bar2.movable=false; bar2.restitution=.8; bar2.friction=.2;
544
Chapter 14: Taking Virtual Tours vplObjects.addDisplayObject3D(physics.getMesh(bar2)); //Add a ball to a random position ballBody.addWorldForce(new JNumber3D(300-600*Math.random(),0,100-200*Math .random()),ballBody.currentState.position); }
6.
Add the textbox method that keeps score and shows the game level. Keep the addChild method the same – don’t add them to your display object as was done previously. This adds your textboxes to the stage as opposed to the game display object.
private function addTextFields():void{ myTextField=new TextField(); hisTextField = new TextField(); myMessage = new TextField(); myMessage.y=20; myMessage.x=400; myMessage.scaleX=1.5; myMessage.scaleY=1.5; myMessage.textColor=0xffffff; myMessage.text=”Level “+String(levelInt)+” Game Goes to 21”; myTextField.y=hisTextField.y=50; myTextField.x=400; hisTextField.x=520; myTextField.textColor=0xffffff; hisTextField.scaleX=1.5; hisTextField.scaleY=1.5; myTextField.scaleX=1.5; myTextField.scaleY=1.5; myTextField.textColor=0xffffff; hisTextField.textColor=0xffffff; myTextField.text=”My Score: “+String(score1); hisTextField.text=”Computer Score: “+String(score2); addChild(myTextField); addChild(hisTextField); addChild(myMessage); myMessage.maxChars=400; myMessage.width=400; }
545
Part IV: Extending PV3D and Beyond 7.
Add your ball collision method.
private function checkCollisions():void { var myTang:Number=Math.atan2(ballBody.currentState.linVelocity.z,ballBody.currentState. linVelocity.x); ballBody.currentState.linVelocity.x=Math.cos(myTang)*ballSpeedIs; ballBody.currentState.linVelocity.z=Math.sin(myTang)*ballSpeedIs; if(ballBody.hitTestObject3D(bar1)){ if(hitOnce){ hitOnce=false; (new hitSoundN() as Sound).play(); }} if(ballBody.hitTestObject3D(bar2)){ if(hitOnce){ hitOnce=false; (new hitSoundN() as Sound).play(); }}
if(ballBody.x100){ changeBoolean(); }} private function changeBoolean():void{ hitOnce=true; }
8.
Add your paddle methods.
private function moveAiPaddle():void { //Very simple AI for the computer-controlled paddle. targetY=(ballBody.z+43); bar2.z +=(targetY-bar2.z)/easing; bar2.x=280; bar2.rotationY=0; } public function movePlayerPaddle():void { if((bar1.z-myDepth/2+34)){
targetX=myDepth/2-mouseY+43; bar1.z +=(targetX-bar1.z)/easing; bar1.x =-280; bar1.rotationY=0; }}
546
Chapter 14: Taking Virtual Tours 9.
In this final stage you have to do a little work. You need to extract from your game loop the appropriate code to insert into your onRenderTick method. This is the heart of your game engine:
//Pong Jiglib Code to be placed in your onRenderTick method moveAiPaddle(); movePlayerPaddle();
checkCollisions(); //Iterate your physics engine physics.engine.integrate(0.2); if(ballBody.x>myWidth/2){ score1++; myTextField.text=”My Score: “+String(score1); } if(ballBody.xmyWidth/2||ballBody.x
560
Chapter 15: Adding Services
(continued)
561
Part IV: Extending PV3D and Beyond (continued) . . . repeat fx:Object as many times as needed . . .
Examining the code above you should be able to identify all the graphical elements (including their layers) used to build your collapsible list box. This code in Flash Builder is set up a little differently to what you typically see in Flex 3: you now have transitions and states blocks that contain the code generated by Flash Catalyst. But if you were used to working with states and transitions in Flex 3 this won’t look too strange to you. The rest of the code is very similar to what you’ve done in Flex 3, just arranged a little differently. It’s amazing how easy it is to create complex interfaces in Flash Catalyst: Adobe has really raised the bar on user experience design. Next, you hook up the data to your code.
Creating a Data Provider In order to get live data into your list, you need to provide it with a data provider. The code for the list component generated by Flash Catalyst is given here:
In order to retrieve data from a server you need to use the HTTPService class. But to get HTTPService into Flash Builder you must first create a declarations tag and inside of that you put your HTTPService. Place your declarations tags before your script block, and give your HTTPService an “id” and result handler. You should recognize that, as you create your results handler, you’re given the option upon code completion to automatically generate the code stub necessary for your listener ’s event handler, as shown in Figure 15.11.
Figure 15-11
562
Chapter 15: Adding Services When you accept the automatic code generation option, the following snippet is generated in your script block for you to complete. protected function myService_resultHandler(event:ResultEvent):void { // TODO Auto-generated method stub }
Flex doesn’t work directly with record sets and query results; it likes working with arrays and objects, so in order to grab the twitter data you need to stuff the results into an ArrayCollection. Do this by first putting a Bindable twitter ArrayCollection in your script block. [Bindable] private var twitterAC:ArrayCollection;
Remember, if you use code completion, the appropriate import declaration is added automatically to Flash Builder.
Grabbing Data from the Web In order to populate the twitter array collection you’ve got to grab the data from the twitter site based upon your search selection. You do this by putting text into your search textbox and clicking the search button shown in Figure 15.12.
Figure 15-12 But right now, your search button and text input aren’t hooked to anything and you need to go into the code and hook them up.
Do this by first adding the click event to the button code shown in the snippet above; as you do that you’re asked to generate the event handler code stub automatically. In the generated code stub add the HTTPService call, and set the URL dynamically with the twitter URL plus the text input from your input textbox of Figure 15.12. protected function button1_clickHandler(event:MouseEvent):void { //Dynamically load data myService.url=”http://search.twitter.com/search.atom?”+textinput1.text; //Run HTTPService myService.send(); }
563
Part IV: Extending PV3D and Beyond Each time you type in a search query and press the search button, info will be loaded into your HTTPService object dynamically. That information then needs to be placed in your twitter array collection using the completed HTTPService code stub here: protected function myService_resultHandler(event:ResultEvent):void { twitterAC = service.lastResult.feed.entry as ArrayCollection; }
The array collection twitterAC is your dataProvider. So to implement it, first remove that designtime ArrayCollection and add the twitterAC array collection as your data provider as shown in the following code snippet:
You need to do one more thing, and that’s to fix your item renderer. The item renderers are in the components package. You need to make the following change in your item renderer (named RepeatedItem1):
1.
Change the Bitmap image to a Flex Image component, and change the source to data. link[a].href, which gets you the user ’s avatar from Twitter. Note that Flash Builder lets you run both Halo and Spark items together. Halo components are the older Flex 3 components, which have both code and graphics grouped together. Eventually, these components will be retired but until that day you can still run them in conjunction with the newer Spark components.
2.
Change:
To:
3.
Change the RichText tags to reference the Twitter data such as the author name and Twitter title:
4.
Finally, after running your application you may find that the images and text aren’t aligned properly; if so just align them correctly in design view.
Running the application grabs Twitter images, titles, and summary text. The complete application can be downloaded from the book’s website.
Adding 3D to Your Flickr Menu In this section, you build a Flickr search application complete with a 3D effects menu. Just as you did in the Twitter application you’re able to type in a search name and get back a list of items. But when you click on one of the Flickr thumbnails, the thumbnail spins in 3D and downloads the higher resolution image into your application for full viewing. The workflow goes from Photoshop, to Flash Catalyst, to Flash Builder.
Using Photoshop To make things a little more interesting, you throw out standard (ugly) scroll bars and buttons and use free-form graphic components from the portrait of the Mona Lisa shown in Figure 15.13. The images for the components can be grabbed using an image grabber such as from Techsmith.com. Jing is free and let’s you highlight the portion of the image you want to snag (or grab). Using Jing you create the search button from Mona Lisa’s mouth, the scroll track from a strip of her picture, the scroll thumb from her nose, and the thumbnail data list image and high resolution image from her full portrait (as shown in Figure 15.13).
Figure 15-13
565
Part IV: Extending PV3D and Beyond Having laid out the image elements in Photoshop, import them into Flash Catalyst so that you can add interactivity.
Adding 3D in Flash Catalyst In Flash Catalyst you create the data list just as you did in the section on Twitter (so you won’t cover that again here). But in addition to this standard data list you’re going to create menu items that spin 360 degrees when clicked upon. You do this by using the new Flash Builder Effects class. To add 3D to your data list items do the following:
1. 2.
After creating your data list, drill down to the base thumbnail item by clicking on it repeatedly.
3.
At the bottom of the TIMELINES panel click on the Add Action button and select Rotate 3D, then stretch out your Rotate3D to 0.5 seconds. Make sure that you save your changes.
In this base item group, you should see three states (Normal, Hovered, Selected). Click on the Hovered state. In the TIMELINES panel, select the hovered > selected menu item.
Okay, that’s it . . . you’ve just added 3D to your data list in less than one minute. If you’ve ever spent an “all-nighter” (as the author has!) working on item renderers for a data list, you’ll really appreciate how easy this is to do!!! Now that you’ve set everything up in Flash Catalyst, it’s time to hook it up in Flash Builder.
Hooking it up in Flash Builder Hooking up the Flickr application is very similar to what you did in the Twitter example, except you add the ability to download a high resolution item when a photo thumbnail is clicked on. Here are the steps for hooking up the application:
1.
Add your HTTPService method with your base Flickr URL at http://api.flickr.com/ services/feeds/photos_public.gne.
2.
Create a Bindable ArrayCollection to hold the results of your HTTPService query.
[Bindable] private var myPhotoFeed:ArrayCollection;
3.
Add a request method to your Search button.
// Request photos based on keyword criteria you insert
566
Chapter 15: Adding Services private function requestPhotos(event:Event):void { // if the photoService is still loading we cancel to prevent errors // then we call again. myPhotoService.cancel(); var params:Object = new Object(); params.format = ‘rss_200_enc’; params.tags = searchTerms.text; myPhotoService.send(params); }
4.
Add a Button_click method to your data list, which sends the index of the clicked thumbnail item to your image loading method.
5.
Add the feed source of your Flash Catalyst high resolution image component.
//Add a Button_click method which tracks which index was clicked on protected function Button_click(event:MouseEvent):void { //Start at index 0 if no index is supplied (-1) if(myList.selectedIndex
After making these changes, run your application. If you hooked everything up correctly you should get the results shown in Figure 15.14. When you search on dogs, up comes a thumbnail list of dogs. When you click on a thumbnail of the dog of your choice, you download its high resolution image.
Figure 15-14 The document source for the main Flickr application is given here. Examining the code, you should be impressed by the small amount of code that accomplishes so much.
568
Chapter 15: Adding Services
You now learn how to hook up services graphically using Flash Builder ’s Data/Services tab. Start by connecting Yahoo’s weather service.
Mining Yahoo ’s Treasure Trove Yahoo has a treasure trove of web services that you can tap into for free. To find them (and instructions on how to access them) go to http://developer.yahoo.com/. At the bottom of the page under “Yahoo! APIs and Web Services” you’ll find more than 30 services that you can tap into for free. Some require that you acquire a free Yahoo Application ID and tack it onto your request string. In this discussion you create a weather widget that doesn’t require a Yahoo ID.
570
Chapter 15: Adding Services
Checking Your Local Weather Clicking the Yahoo Web Services Weather link takes you to Yahoo’s Weather RSS Feed page at http://developer.yahoo.com/weather/. Here you can find all the information you need to build your query. The Weather RSS feed is a dynamically generated feed based on zip code or Location ID. According to Yahoo’s documentation, the weather RSS feed request follows simple HTTP GET syntax: start with a base URL and then add parameters and values after a question mark (?). Multiple parameters are separated by an ampersand (&). For the Weather RSS feed there are two parameters: ❑
p for location
❑
u for degree units (Fahrenheit or Celsius).
The location parameter p is required. Use this parameter to indicate the location for the weather forecast as a zip code or Location ID. http://weather.yahooapis.com/forecastrss?p=location
For example, to get weather for Yahoo! Headquarters in Sunnyvale, CA, use the zip code for Sunnyvale (94089): http://weather.yahooapis.com/forecastrss?p=94089
Or use the Location ID for Sunnyvale (USCA1116): http://weather.yahooapis.com/forecastrss?p=USCA1116
The location parameter can be a US zip code or a location ID. To find your location ID, browse or search for your city from the Yahoo weather home page. The weather ID is in the URL for the forecast page for that city. The optional u (units) parameter indicates the degree units for the weather forecast. By default, Yahoo! Weather returns temperature information in degrees Fahrenheit. Use the u parameter to explicitly specify the degree units in Fahrenheit (f) or Celsius (c). The units parameter is case sensitive. For example, to get the weather forecast in Sunnyvale, CA in Celsius: http://weather.yahooapis.com/forecastrss?p=94089&u=c
It’s important that you understand the details of how you query the information. You use the http://weather.yahooapis.com/forecastrss as your base HTTPService URL and pull the zip and degree information from components built in Flash Catalyst.
571
Part IV: Extending PV3D and Beyond Examining Your Weather XML If you enter in the query string discussed above you get back a web page that contains the location and weather conditions for Sunnyvale, CA. But you can’t stop here, you need to right-click and perform a view source on the page so you can access the RSS Feed. In doing so, you get the following XML code: Yahoo! Weather—Sunnyvale, CA http://us.rd.yahoo.com/dailynews/rss/weather/Sunnyvale__CA/*http://weather. yahoo.com/forecast/USCA1116_c.html Yahoo! Weather for Sunnyvale, CA en-us Wed, 08 Jul 2009 3:56 pm PDT 60 Yahoo! Weather 142 18 http://weather.yahoo.com http://l.yimg.com/a/i/us/nws/th/main_142b.gif Conditions for Sunnyvale, CA at 3:56 pm PDT 37.39 -122.03 http://us.rd.yahoo.com/dailynews/rss/weather/Sunnyvale__CA/*http://weather. yahoo.com/forecast/USCA1116_c.html Wed, 08 Jul 2009 3:56 pm PDT Current Conditions: Fair, 26 C Forecast: Wed—Mostly Clear. High: 24 Low: 13 Thu—Partly Cloudy. High: 23 Low: 12 Full Forecast at Yahoo! Weather (provided by The Weather Channel) ]]>
572
Chapter 15: Adding Services USCA1116_2009_07_08_15_56_PDT
It’s important that you study this XML carefully. You need to understand its structure so you can properly build your weather widget. This is true for any service that you’ll be parsing. Now that you’ve examined your XML structure you can properly create your widget interface in Flash Catalyst.
Creating Your Web Service Code In the previous section on Flickr, you hand-coded your HTTPService. But in this section, you use Flash Builder ’s new Data/Services feature to grab all of the XML above automatically and bring it into a Flex Builder project. Here are the steps:
1. 2. 3.
Create a Flex project and call it MyWeatherData. Click on Flash Builder ’s Data/Services Tab and Select Connect to Data/Service From the graphical menu shown in Figure 15.15, select HTTPService.
Figure 15-15
4.
In the next panel input the Service name as myWeatherCode, the Operation as getMyData, and the URL as http://weather.yahooapis.com/forecastrss?p=94089&u=c. This enables Flex to analyze your data, and set up your parameters as shown in Figure 15.16. When you hit the Finish button, Flex creates your service and reminds you that you still need to configure and bind your data.
573
Part IV: Extending PV3D and Beyond
Figure 15-16
If you open up the services package that you just generated (services.myweathercode) in the package explorer, you’ll find two files (_Super_MyWeatherCode and MyWeatherCode). Opening up _ Super_MyWeatherCode you’ll find the main constructor function here: // Constructor public function () { // initialize service control _serviceControl = new HTTPMultiService(); var operations:Array = new Array(); var operation:Operation; var argsArray:Array; operation = new Operation(null, “getMyData”); operation.url = “http://weather.yahooapis.com/forecastrss”; operation.method = “GET”; argsArray = new Array(“p”,”u”); operation.argumentNames = argsArray; operation.resultType = Object; operations.push(operation); _serviceControl.operationList = operations; model_internal::initialize(); }
You should recognize your input parameters (p and u), your base URL, your GET method, and a new HTTPMultiService() method that takes the place of your hand-coded HTTPService method.
574
1.
Next you create a custom class that enables you to generate a return form. In the Data/Services tab right-click on the getMyData method and choose Configure Return Type . . . and in the panel text field create a new custom data type, input a name for your return class (MyWeatherReturn). Then click Next.
2.
In the New Data Type panel you can enter in data values in the Enter Value column (but your old values should be there) and hit the Next button.
Chapter 15: Adding Services 3.
In the Modify Properties or Return Type panel shown in Figure 15.17, you can limit what’s returned. In this case, choose channel, which lets you view all data.
Figure 15-17
4.
Finally, you can generate a form automatically that you can use to query your Web Service and examine its complete set of XML returns. You do this by right-clicking on getMyData . . . MyWeatherReturn object in the Data/Services tab and choose Generate Form. All the defaults should be fine except that you deselect Make result form editable, as you won’t be editing the data. Click the Finish button to generate the entry and return forms.
5.
In Design view, you’ll find both an entry and return form. They may be overlapping so move them apart by clicking and dragging on them and extend your Flex window so you can view all the returned data.
6.
Run the programs and input your zip code – and you get the very ugly return form shown in Figure 15.18.
P
41042
U
1
GetMyData
MyWeatherReturn Title
Yahoo! Weather - Florence, KY
Link
http//:hus.rd.yahoo.com/dailynews/rss/weather/florence,
Description Language LastBuildDate Tl
Yahoo! Weather for Florence, KY on-us Thu, 09 Jul 2009 10:52 am EDT 60 Yweather_location
City
Florence
Region
KY
Country
US
Figure 15-18
575
Part IV: Extending PV3D and Beyond Now at this point, if you’re a designer, you may be thinking . . . “Why bother – this is really ugly”. But if you’re a developer and you’ve ever had to hand-code all of this in Flex you’re now singing the Hallelujah Chorus. This is truly amazing: what would have taken you hours to hand-code you’ve just accomplished in less than five minutes without writing a single line of code. You can now spend your time beautifying it! And when round tripping is instituted you’ll be able to take your FB programs back and forth between FB and FC, hooking up interactive interfaces to code.
Making Your Widget Interface If you don’t have Photoshop (or Illustrator) don’t despair, you can build complete interfaces in Flash Catalyst using Wireframe Components, adding interaction as needed, and then pass that interactive widget into Flash Builder where you can throw on a few graphics where needed. Or you could go ahead and just build the entire interface in Flash Builder. For the weather app, here’s your design doc. Your widget consists of two interchangeable panels (input on one and output on the other). After entering in the appropriate data you hit a send button and your panel will fly out, revealing the result of your request. Your panel sizes will be 240x320.
Input panel Your input panel has the following components: ❑
Input Textbox for your zip code
❑
Radio buttons for selecting Fahrenheit or Celsius selection
❑
Send request button
Output panel Your output panel has the following components: ❑
Description textbox (city name and zip code)
❑
Temperature
❑
Place for cloud graphic
❑
Conditions textbox
❑
Two-day forecast
❑
Refresh, Full Forecast, and Return buttons
The panel configuration above can be created in Flash Catalyst in less than five minutes and is shown here in Figure 15.19. Now it’s time to run down the hall and get your graphic designer. It’s functional, but not pretty!!!
576
Chapter 15: Adding Services
Figure 15-19 At this point all you have to do is import your Flash Catalyst file into Flash Builder and hook up the code you generated in the last section. This is left as an exercise to the reader and the finished application can be downloaded from the book’s website.
Building a CNN News Feed As in the case of Yahoo, CNN has a treasure trove of news feeds ranging from top stories to insider blog posts. You can access them at http://www.cnn.com/services/rss/?iref=rsssvcs. And for the purposes of this discussion you create a simple “Top Stories” news feed (at http://rss.cnn.com/rss/ cnn_topstories.rss) using just Flash Builder. As before, you want to go to the news feed link (http://rss.cnn.com/rss/cnn_topstories.rss) for top stories and view it on the web, and then perform a view source to take a look at its XML. This helps you select the correct node for parsing in Flash Builder.
Connecting Your Service The procedure for creating the HTTPService is identical to the last section on the weather widget, except you don’t have any query parameter. Here are the steps for creating the Service connection:
1. 2. 3.
Create your Flex Builder application and name it ACNNewsFeed.
4.
You now need to configure a return class. In the Data/Services panel right-click on the getFeed() object and select Configure Return Type.
Click on Data/Services, select Connect to Data/Service, select HTTPService, and click Next. In the Configure HTTP Service panel in the Service name field, type in CNNewsFeed, and in the Operation input box under Operation type in getFeed and for URL paste in the top stories rss link http://rss.cnn.com/rss/cnn_topstories.rss and click the Finish button.
577
Part IV: Extending PV3D and Beyond 5.
In the Configure Operation Return Type panel in the Create a new custom data type input box type myNewsFeed and click Next.
6.
In the Create New Data Type panel select the Enter complete URL including parameters radio button and in the URL to fetch input box paste in the news feed link from step 3 and click Next.
7.
In the Modify Properties of Return Type panel you can now select the node you want to bring in. Select item from the drop down combo box and select finish. In the Data/Services panel your getFeed() object should now have the MyNewsFeed return class attached to it.
After connecting your web service you need to bind it to a list component.
Binding Your Data You now need to bind your data to a list component that you’ll use to display your top story titles, and when selecting a time its news feed will appear. Here are the steps:
1.
Go to design view, open up your components panel and drag a list box to the stage from the data controls folder and size it appropriately.
2.
Right-click on the list box and choose Bind to Data . . . accept the New service call and in the Bind to field drop down box select title. This populates your list box with the title of your feed. Then click OK. A binding icon should appear at the top of your list box.
3.
Right-click on your list box and choose Generate Details Form. Deselect Make form editable, and select Make a new service call to get details and click Next.
4.
In the Property control mapping panel select and rearrange the links that you want to appear in your news feed (title, pubDate, description, and link), and click Finish. The feed form is generated in design view. Reposition the form appropriately. Try running the program.
When you run your form you’ll find that when you click on any item in the list only the first item gets fed to your form. You need to configure your data to get it running properly. If you’re a designer, it’s time to run screaming down the hall to get your developer to code this side for you.
Configuring Your Data Hooking up your data isn’t difficult; you just need to look at it from the eyes of a developer. To this point you’ve been spoiled – the application has practically been built without writing a single line of code. But to finish it you’ve got to put on your Flex hacking cap. Start by identifying the problem and follow your nose:
The Problem The problem is that when you click any item in your list box only the first news feed shows up. This means that your list change handler method needs a little work.
578
Chapter 15: Adding Services Code before The list change handler is shown here: protected function list_selectionChangedHandler(event:IndexChangedEvent):void { getFeedResult2.token = cNNewsFeed.getFeed(); }
This method calls a responder which gets the news data.
But the responder method shown above only grabs the zero index of the lastResult, which is your first news feed. In order to get other news feeds you need to change the lastResult index to correspond to what you’ve clicked on in your list.
Code after To change the lastResult index start by creating a new private variable called myIndex. private var myIndex:int=0;
Then set the myIndex variable in the lastResult responder method as shown here:
Next you need to set the myIndex variable to the index of what you’ve clicked on. In the list change handler place the following line of code: myIndex=list.selectedIndex;
The complete list change handler method is shown here: protected function list_selectionChangedHandler(event:IndexChangedEvent):void { //Set List Selected Index myIndex=list.selectedIndex; //Get Feed by calling responding method getFeedResult2.token = cNNewsFeed.getFeed(); }
Doing a little Styling Now that you have all this data hooked up you want to do a little styling to your application.
1.
First in the description section (shown in Figure 15.20), put a TextArea component and link it to the news feed description.
579
Part IV: Extending PV3D and Beyond
Figure 15-20
2.
Next create a Full Story link button (shown in Figure 15.21) and attach to it the URL method.
Figure 15-21
580
Chapter 15: Adding Services When you click on this link button Click for Full Story a full-story html page is brought into your browser. private function getMyURL(event:MouseEvent):void{ var req:URLRequest = new URLRequest(myNewsFeed.link); try { navigateToURL(req); } catch (e:Error) { trace(“No URL Available”); } }
This method uses your feed link as a dynamic URL request parameter, and catches bad URLs in its catch statement. The complete source code for the application is given here:
582
Chapter 15: Adding Services If you’ve ever written an RSS news feed before you’ll certainly be impressed with how easy this was to create. When running the program you get the results shown in Figure 15.22.
Figure 15-22 The results shown in Figure 15.22 are functional. Whenever you click on a link in the list box on the left the appropriate feed appears on the right. But it’s really ugly. So run down the hall and get your designer.
Summar y In this chapter, you learned to hook up Web Services using Flash Catalyst and Flash Builder. You built a Twitter viewer, Weather checker, Flickr picker, and CNN news reader. But there’s more . . . WSDL, PHP, Coldfusion, BlazeDS, and LCDS (a full gamut of wondrous click-and-drag connectivity). To cover all these topics thoroughly would take an entire book (or more). But in this chapter you got the basics and with Flash Builder, data connectivity is made easy, giving you more time for development. The next generation of creative Flash applications is on its way! An awesome place to go to find out more, beside the book’s website, is Sujit Reddy’s blog at http://sujitreddyg.wordpress.com/. Sujit has a large number of video tutorials on hooking up Web Services to Flash Builder.
583
Flash & Math: Exploring Flash 10 and Beyond When Flash 10 was released, the Away3D team immediately embraced it and updated their application to take advantage of some of its features. But just enhancing your 3D application so that it uses the Flash 10 features isn’t enough to make anyone excited. The present 3D work flow for software like Away3D and PV3D is too time consuming and complex. The next successful 3D Flash engine won’t just concern greater speed but will also be about enhanced work flow. Adobe has got it right . . . programs like Flash Catalyst are models of what the next generation of 3D software must do: create sites and games using techniques that accelerate work flow by generating code through graphical techniques. Like Flash Catalyst, which generates code by graphical techniques, there needs to be something like a “Papervision Catalyst” that generates 3D websites just as easily. In this chapter, you start with taking the pool shooting game developed in Chapter 10 and recreate it using Flash Catalyst and Flash Builder. You discuss optimizing the PV3D engine to run more efficiently in the Flash 10, take a look at faster options, and develop other possible 3D rendering schemes using drawTriangle class. Finally, you take a look at Flash Builder ’s new layout functionality. Importantly, no chapter like this is ever complete. Technology is moving so fast that as soon as a topic is published about the newest thing – it’s not! To help address this problem the book’s website is constantly being updated on the newest technology.
Part IV: Extending PV3D and Beyond
Rebuilding the 3D Pool “Shooting” Rebuilding the pool shooting game from Chapter 10 in Flash Builder begins with laying out its states in Flash Catalyst, then importing Flash Catalyst into Flash Builder, and laying PV3D onto those states. Flash Catalyst takes the chore out of programming states in Flex, and adds transitions and interactivity using the graphical user interface instead.
Laying out Your Game’s Framework Start by laying out the graphics that you need for the game’s states in Photoshop as shown in Figure 16.1: five buttons, large outer-space back ground, and gun panel screen. You don’t have to worry about laying out your textboxes in Photoshop. You can use the wireframe components in Flash Catalyst for your text.
Figure 16-1 After you lay out your graphics, save your Photoshop file and open it up in Flash Catalyst. Checking the code view, you’ll find that the following code has been automatically generated for you, which is also shown here:
586
Chapter 16: Flash & Math: Exploring Flash 10 and Beyond You should recognize that, in the code above, all your graphics have been automatically embedded. At the heart of the Flash Catalyst process is FXG. FXG stands for Flash XML graphics and is a subset of MXML (including only graphics tags). It’s a declarative syntax for defining graphics in Flex. Why FXG? The purpose of FXG is to develop “componentized graphics” for Flex. Flex has a great XML framework, so it’s a natural extension to building a graphically driven MXML framework. What Adobe did was to build a set of ActionScript objects that represented all the graphics in Flash and mapped them into XML for Flex using the rules of MXML. Out of this approach came two great benefits: Other programs such as Photoshop and Illustrator can produce FXG graphics which can easily be brought into Flex. Separating graphics from code makes it easier to skin components and mix graphics with data (such as adding data binding and even handlers), extending present capabilities and ease of connection. As a result, this makes it much easier to develop rich Internet applications (RIAs). Finally, how does FXG relate to Spark? FXG is used in Spark skins to draw the visual elements of the class. An FXG file consists of a single definition and an optional library that’s contained within a root tag. An FXG document fragment can include zero or more containers and graphic elements. An FXG file can be a standalone *.FXG or *.MXML file. The following is a simple example of an FXG document fragment inside an MXML file:
The application above (taken from Adobe docs) draws a diagonal line on the screen from the (0,0 coordinate to (100, 100) inside of a panel using a FXG fragment. It’s FXG that enables Flash Catalyst to do what it does. With that bit of a detour into FXG, you now get back to the business of building your game by adding game states.
587
Part IV: Extending PV3D and Beyond Adding Game States in Flash Catalyst The 3D pool shooting game consists of the following states: ❑
Intro State
❑
Play State
❑
Win State
❑
Lose State
❑
Cancel State
To create the five states shown in Figure 16.2, all you have to do in Flash Catalyst is hit the Duplicate State button in the PAGES/STATE panel. Then give each state an appropriate name. One of the great things about Flash Catalyst is that it’s so easy to create transitions between states graphically, whereas Flex 3 required you to hand-code each transition.
Figure 16-2
As you’re graphically creating your states, the following code is being built for you in the background.
Just as you did in the previous chapter, you want to create your buttons and all the interactivity required for each of the five states. But you’ve got a problem. Each time you run your program it takes you back to your original state. So how do you test the button interactivity of the other states? You’ve got to change the Default State.
Figure 16-3
588
Chapter 16: Flash & Math: Exploring Flash 10 and Beyond As shown in Figure 16.3, to change your default state, all you have to do is right-click on the state you want to start at and select Set as Default State. Next time you run your program you’ll be able to test the interactivity of that state. After testing, make sure that you change your default state back to Intro. Once everything has been laid out in Flash Catalyst, all the required interactivity added, and the appropriate textboxes added, you’re ready to import it into Flash Builder. All of this is accomplished in the same way as in the previous chapter so there’s no need to cover it again here. To see the completed application, check out the book’s video tutorial on this section.
Adding Code in Flash Builder After importing your file into Flash Builder, code the button’s interactivity, and the 3D pool shooting game of Chapter 10 Here are the steps:
1. 2. 3. 4.
Add Jiglib, org (PV3D/Lively3d), and caurina class folders to your application. Add your assets folder and LineOut class. Migrate all your import statements and variables. In the Play_click() method add the code required to take you to the play state and the appropriate initialization statements.
protected function Play_click():void { var state:String = currentState; if ( state == ‘Intro’ ) { currentState=’Play’; initPV3D(); myGameState(1); myTester.y=-1000; }}
5.
Add an initialization method to your application tag.
creationComplete=”init()”
When your program starts up it automatically goes to the Intro state (since that’s how you set it in Flash Catalyst) and runs the init() method which starts your initial sound playing.
6.
Add your init() method and your sound method to your program.
private function init():void{ myGameState(0); }
7.
Then transfer your methods (testing as needed). As you bring over various methods you’ll start getting errors because your text boxes aren’t hooked to your score, timer and other items. Connect these as you go. Once everything is connected your code should run great!
589
Part IV: Extending PV3D and Beyond After performing all these steps you should be running your game with no problem. You’re now running your Flash 9 game in Flash 10. To speed things up a little you need to optimize this application so it can take advantage of the new features available in Flash 10.
Optimizing PV3D for Flash 10 It may surprise you that PV3D runs in Flash 10 without any problems, and as a result it’s easy to port PV3D into Flash builder and layer 3D Collada scenes onto your Flash Builder programs. But, as in the case of the pool shooting game, doing so doesn’t give you a performance enhancement. That’s right – the Flash 10 engine is useless, unless you take advantage of its Vector3D, native z, and Matrix3D, and other classes. At this point, you’ve probably got a few questions about the process.
What do you do? The porting process of optimizing PV3D for Flash 10 is simple. The process is essentially one of moving your Vector3D and Matrix3D classes into your PV3D core, incorporating your native z advantage, and eliminating superfluous code: of which there’s a large amount as much of the processes are now encapsulated in the Flash 10 player.
How long will it take? If you really know what you’re doing (and have nothing else to do) it’s a two week development activity.
Will you get a performance boost? Not really! The author has done extensive testing on this port and hasn’t found a significant enhancement (or at least not enough to write home about). The pot of 3D gold isn’t just a port of PV3D but a complete architectural shift that takes advantage of technologies like Pixel Bender and Alchemy – which run at optimized x5 or x10 performance speeds. Ralph Hauwert, the driving force behind PapervisionX, has already demonstrated that you can push around +300,000 3D particles, in real-time on the screen, using Alchemy and Pixel Bender. You can check out his web demo (shown in Figure 16.4) at http://www.unitzeroone.com/labs/alchemy PushingPixels/.
Figure 16-4
590
Chapter 16: Flash & Math: Exploring Flash 10 and Beyond But there’s a problem . . . Alchemy and Pixel Bender don’t run on the same architectural platform as many of the new 3D Flash 10 native rendering classes such as Vector3D, Matrix3D, and Perspective Projection. You can’t use these features in combination with Alchemy – and that’s a bummer. So the appropriate architecture to produce results like those obtained in Figure 16.4, on a scale the same as PV3D, has to be created. Development activities like this take months of work. As a result, you have another problem . . . developers are reluctant, in such a fluid environment, to pour hours of work into technologies that might disappear or be significantly enhanced in the near future, causing their work to become obsolete overnight. Sometimes it’s a wait-and-see game as architectures shift and change. To learn more about Alchemy, check out Adobe Labs at http://labs.adobe.com/ technologies/alchemy/.
Until these 3D rendering models are developed in Pixel Bender (or another C slave) and Alchemy, you’ll be using Adobe’s native 3D classes (which are really amazing). And in the next section, you take a look at a number of examples using the newest framework.
Rendering 3D Models in Flash 10 When you first started working with Flash CS4 you were probably amazed at how easy it was to create flying post card applications. And as long as everything remained flat (and you create the appropriate sorting algorithms) you had no complaints. But as time went on you probably started wondering how you could bring in your 3D models. The real hallmark of PV3D is its ability to bring in 3D models. And why can’t Flash 10 do the same? Isn’t it supposed to be better than Flash 9? Well, it’s a little more complicated than that. Flash 10 was a complete architectural shift that incorporated a native z component and various 3D packages which, at first, no one knew how to use. It wasn’t that Flash 10 couldn’t import and render your Collada models, it was just that no one had written the classes to do it yet. In the following section, you go through a series of 3D examples ranging from rendering flying post cards to rendering full 3D models, illustrating the use of the many new 3D classes in Flash 10. A full treatment of this topic would take several chapters of this book, but there’s enough here to get you started. Unfortunately, the information is a little compressed. But for each example given, a corresponding tutorial video has been created on the book’s website.
Creating a 3D Image Ball The image ball in Figure 16.5 is an enhanced Flex 4 version of the one created by Flash & Math (at http://www.flashandmath.com/). When you click on the images in the image ball, its high resolution version is brought to the screen for viewing. The port to Flex 4 was non-trivial, but the big thing about having it in Flex (MXML and ActionScript) was that you can drag in Flex components, letting you harness data connectivity easily in order to build more complex applications.
591
Part IV: Extending PV3D and Beyond
Figure 16-5
The following discussion demonstrates some of the challenges present when porting a CS4 Flash program over to MXML. The CS4 Flash code from Flash and Math is thoroughly documented and you should download it and go through their version of the documentation to understand how it was created. Here you only deal with the port itself, not how the image ball works. Here are the highlights of the port from the Flash & Math version to Flex 4.
1.
You can’t use addChild to add a sprite directly to the stage of a Flex MXML project as you do in Flash CS4. So create a canvas (cs4Canvas) and add your sprite to that canvas using rawChildren.
cs4Canvas.rawChildren.addChild(board);
2.
Flash and Math pull their thumbnail image assets from the Flash library using linkage. Since Flex doesn’t have a Flash Library you can’t take this approach, so one approach is to use the embed tag for the thumbnails (all 46 of them – remember you can use Flash Catalyst to generate all the embed statements for you automatically).
[Embed(source=”thumbs/pic1.jpg”)] private var Small1:Class;
3.
In the Flex version of the code, the embed method that Flash & Math uses (setUpPics()) won’t work, so you must use the BitmapAsset class to create a thumbnail array. And then you must exchange your embed class for their movie class.
thumbsArray[0]=[new Small1() as BitmapAsset];
4.
592
You must rewrite the Flash and Math Vector array declarations using a push method to load your vector arrays – their method of loading these arrays doesn’t work in Flex.
Chapter 16: Flash & Math: Exploring Flash 10 and Beyond thetaStep.push(0,60,36,30,36,60,0); jLen.push(1,6,10,12,10,6,1); phiTilt.push(-90,-60,-30,0,30,60,90)
5.
Just as with a formal class structure, you must make sure that your variables have the private declaration in front of them, and that all declarations are contained in a function (within your script tags). In addition, you must create an init() method and call it from your MXML tags (this acts like a constructor function).
6.
You must add an image forward and backwards button to your program so that once the user double-clicks on a thumbnail image its high resolution image is called. Using these buttons your client can now surf the images (without going back and forth between image and image thumbnails as is the case in the Flash and Math version). This is a big improvement to the code. Implementing this requires that you write a small string routine that extracts the numbers from the image name so you could start at that image and go sequentially.
var myStringIs:String = picsArray[i][j]; myStringIs=myStringIs.split(“.”).join(); myStringIs=myStringIs.split(“c”).join(); splitArray=myStringIs.split(“,”); stringNumIs=Number(splitArray[1].toString());
In addition, you need to create the navigation code (shown here) for your move forward and back buttons to call the correct images. private function moveForward(e:MouseEvent):void { stringNumIs++; stringNumIs=stringNumIs%47; if(stringNumIs==0)stringNumIs=1; loader.load(new URLRequest(“images/pic”+stringNumIs+”.jpg”)); } private function moveBackwards(e:MouseEvent):void { stringNumIs-; if(stringNumIs==0)stringNumIs=46; loader.load(new URLRequest(“images/pic”+stringNumIs+”.jpg”)); }
This works fine as long as your images are sequentially numbered, but who can remember which image is which? So in the next iteration (which is left as an exercise for the reader) you need to extend this routine to handle any image name. The complete code can be downloaded from the book’s website.
593
Part IV: Extending PV3D and Beyond
Using drawTriangles to Build a Shaded TIE Fighter When Flash 10 was first released there were a number of posts speculating whether 3D models could be brought into CS4. The problem was that there were a bunch of powerful tools, but they worked a little differently compared to what most programmers were familiar with. The key to rendering models in 3D is using the drawTriangles class. In this section you render a Blender TIE fighter model. It’s great starter code, but there’s still much work to be done to create a full blown model renderer. The good news is that you don’t have to start from scratch; PV3D has laid the foundation. Figure 16.6 shows a shaded TIE fighter built completely in CS4. It was created in Blender, and uses drawTriangles to render it. The shaders come directly from Ralph Hauwert’s fast light maps and the background perlin noise clouds come from flepstudio (at http://www.flepstudio.org).
Figure 16-6
This program culls, has its own light class with brightness (here it alternates in brightness), depth sorts, and displays concavity with no problems.
Flat Shading in CS4 Here’s how flat shading works in CS4. The light class is a point light with brightness (contained in the org/lively3d/lights/ folder of your download). It gives you the position of your light source, brightness, light color, ambient color, and specular level. Now doing real light calculations in 3D is very CPU intensive. To get a good light effect and avoid all this processing, there’s a trick.
594
Chapter 16: Flash & Math: Exploring Flash 10 and Beyond The trick The lightColor variable is the color of your light source. The ambientColor variable is not true ambient light. It’s the first and second parameter in a gradient fill (the third one is your light color), and it governs the color that your object will take on. The specularLevel variable adjusts the number of shades between your middle ambient color and light color, and gives the appearance of making your surface shiny at low numbers – hey, it’s a great hack!
How the gradient fill works The beginGradientFill method has several parameters, but the only ones used here are type, color, alphas, ratios, and matrix: beginGradientFill(type, colors, alphas, ratios, matrix)
They do the following: ❑
type — specifies which gradient type to use: linear or radial
❑
colors — an array of color values used in the gradient
❑
alphas — an array of alpha values corresponding to the colors
❑
ratios — define the percentage of the width where the color is sampled at 100 per cent.
❑
matrix — a transformation matrix which controls scale, skewing, and location of your gradient appearance.
How the light map is implemented A BitmapData object is created named tempmap, which is just a simple strip 255 pixels long and 1 pixel wide. The gradient is calculated using the ambient and light colors, and the gradient is drawn into the tempmap and returned. The process described above is demonstrated in the code here, which comes from Ralph Hauwert’s LightMaps class and is used to create the light maps for flat, cell, phong, and gouraud: var tempmap:BitmapData = new BitmapData(255,1,false,0); var s:Sprite = new Sprite(); var m:Matrix = new Matrix(); m.createGradientBox(255,1,0,0,0); s.graphics.beginGradientFill(GradientType.LINEAR, [ambientColor,ambientColor,lightColor],[1,1,1],[0,255-specularLevel,255],m); s.graphics.drawRect(0,0,255,1); s.graphics.endFill(); tempmap.draw(s); return tempmap;
595
Part IV: Extending PV3D and Beyond Now that you understand how a simple light map is created, the steps to creating a flat shaded model are easy.
Creating a flat shaded model You create your light map in this way: _colors = LightMaps.getFlatMapArray(lightColor, ambientColor, specularLevel );
You follow these steps:
1.
Return a cosine (zd) from the dot product of your light vector with your triangle normal vector. This gives the cosine of the angle of your triangle face normal to your light source. If your light source is at 90 degrees to your object triangle (no light hitting it) then the cosine value (zd) is zero. If your triangle is directly facing the light source then your cosine is 1 (completely lit).
AVect[0]=new Vector3D(dispVec[facesVec[curFace][0]].x,dispVec[facesVec[curFace][0]] .y,dispVec[facesVec[curFace][0]].z); AVect[1]=new Vector3D(dispVec[facesVec[curFace][1]].x,dispVec[facesVec[curFace][1]] .y,dispVec[facesVec[curFace][1]].z); AVect[2]=new Vector3D(dispVec[facesVec[curFace][2]].x,dispVec[facesVec[curFace][2]] .y,dispVec[facesVec[curFace][2]].z); AVect[3]= AVect[0].subtract(AVect[1]); AVect[4]= AVect[1].subtract(AVect[2]); AVect[5]=AVect[4].crossProduct(AVect[3]); AVect[6]=new Vector3D(light.x, light.y, light.z); var mag1:Number=AVect[5].length; var mag2:Number=AVect[6].length; var zd:Number = AVect[6].dotProduct(AVect[5])/(mag1*mag2);
In CS4, you totally eliminate PV3D’s Numbers class, as all the calculations provided by that class are now internal to CS4. So for example, a dot product of two matrices in CS4 is given by: var zd:Number = AVect[6].dotProduct(AVect[5])/(mag1*mag2);
2.
Calculate the index value from your zd by multiplying it by 255 (hex 0xff) and grab the respective color from your _colors array. Essentially, as your triangle turns away from your light source, the hex value is reduced – effectively darkening your pixels.
zAngle = zd*0xff; //0xff if hex for 255 currentColor = _colors[zAngle];
596
Chapter 16: Flash & Math: Exploring Flash 10 and Beyond 3.
Render your triangle and fill it with your calculated light map color as shown in Figure 16.7.
spObjImage.graphics.beginFill(currentColor); spObjImage.graphics.drawTriangles(vertices);
The code above uses drawTriangles exclusively, where PV3D draws individual lines. Theoretically, using drawTriangles should be faster.
Find Light Map Index and Color
Render Triangle
Calculate Cosine
Create Light Map
Figure 16-7
Using drawTriangles The heart of the code is the drawTriangles method, which can be used to place individual triangles on the stage and fill those triangles with shades, gradients, or materials. The TIE fighter is created by building up, sorting, and fitting together a large number of these triangles. The position and connecting data for these triangles are generated from a Blender XML data file. This data file is created from a Blender XML exporter (created by the author) found on the book’s website. The abbreviated Blender XML data file, shown here, consists of vertex position and triangle connection (or triangle face) data. 0.631775,0.036783,1.471956,0.629989,-1.278971,0.707213,0.626437,-1.274558,-0.814632 ,0.624669,0.045608,-1.571733,0.772240,1.168502,. . . more position values go here . . . 19,18,24,20,19,24,21,20,24,22,21,24,23,22,24,18,23,24,31,26,25,26,27,25,27,28,25,28 ,29,25,29,30,25,30,31,25,50,9,10,4,50,10,50,3,9,51,10,11,5, . . . more face values go here . . . 0.000000,0.000000,0.000000,0.000000,0.000000,-0.000000,1.000000,1.000000,1.000000
597
Part IV: Extending PV3D and Beyond This data is parsed in the main program and used to construct your model’s triangles. The method for parsing the vertex and face data is given here: private function setVertices(myData_xml:XML):void { //Parse vertex and face data var mySlitVert:Array=myData_xml.myPrimSet[0].myVertices.split(“,”); var mySlitFace:Array=myData_xml.myPrimSet[0].myFaces.split(“,”); numVertices=(mySlitVert.length-1)/3; var myScale:Number=20; for(i=0;i uv:points: 00, 01, 10, 11). But drawTriangles doesn’t know what to do with this. It wants one uv point for each vertex. The way around this problem is to collect all similar uv data points per vertex and assign a new vertex number for non-unique points. So for example: (point 4: 0,0,0 -> uv:points: 00, 01, 10, 11)
becomes: point point point point
4: 5: 6: 7:
0,0,0 0,0,0 0,0,0 0,0,0
-> -> -> ->
00 01 10 11
Points 4 through 7 are your unwrapped points and points 0 through 3 are the four base pyramid points, which aren’t unwrapped and as a result don’t need to be assigned new vertex numbers. The new vertex numbers have the same vertex coordinates as shown above – only the point assignment for the triangular fan is changed. The number of extra points you need to add is entirely dependent on how you unfold your object. If you use the hole punch method, you only need four extra vertices for an entire figure. As in anything, there’s no free lunch. You always give up something to gain something.
How the Code Works The code sorts points into unique vertices and uv numbers (creating a one-to-one mapping). If unique data can’t be found, then new vertices are assigned, creating a one-to-one mapping. The whole process starts by creating a Blender XML file and Photoshop image. This is done by using the Blender XML Exporter discussed on the book’s website. The Blender XML file is imported into the
600
Chapter 16: Flash & Math: Exploring Flash 10 and Beyond program, parsed, and sorted. Then the following sorter code is used to create the one-to-one mapping scheme discussed above. Here are the steps:
1.
The code starts by grabbing the index and vertex data created from the Blender XML export.
for (var i:int = 0; i< myFaceNum; i++) { //Grab Indices from Blender XML indices.push(int(mySplitFace[12*i])); indices.push(int(mySplitFace[12*i+1])); indices.push(int(mySplitFace[12*i+2])); //Grab UV Data from Blender XML myUVDatArray.push(Number(mySplitFace[12*i+3])); myUVDatArray.push(Number(1-mySplitFace[12*i+4])); myUVDatArray.push(Number(mySplitFace[12*i+5])); myUVDatArray.push(Number(1-mySplitFace[12*i+6])); myUVDatArray.push(Number(mySplitFace[12*i+7])); myUVDatArray.push(Number(1-mySplitFace[12*i+8])); }
2.
The raw data above is sorted and a one-to-one mapping scheme is created.
//Sorting Program myFertsNum=indices.length; myVertsNum=verts.length/3-1;//iteration number for (var j:int = 0; j< myFertsNum; j++) { for (var k:int = j+1; k(); private var data:Vector. = new Vector.(); //Atom’s physical properties private var type:String; private var radius:Number; private var myColor:Number; public public public public public public public
var var var var var var var
xpos:Number ypos:Number zpos:Number vx:Number = vy:Number = vz:Number = mass:Number
= 0; = 0; = 0; 0; 0; 0; = 1;
//Gradient properties private var myType:String = GradientType.RADIAL; private var alphas:Array = [1, 1]; private var ratios:Array = [1, 100]; private var matrix:Matrix = new Matrix();
//var colors:Array =
[0x00FF00, 0x000088];
public function Atom(type:String) { this.type = type; init(type); } public function init(type:String):void {
(continued)
613
Part IV: Extending PV3D and Beyond (continued) matrix.createGradientBox(200, 40, 0, 0, 0); //Atom’s switch case method. Selects the atom under consideration switch(type) { case “H”: //myColor = “colors/white.jpg”; myColor = 0xffffff; radius = 25; break; case “C”: //myColor = “colors/grey.jpg”; myColor = 0xbfbfbf; radius = 70; break; case “O”: //myColor = “colors/red.jpg”; myColor = 0xe60501; radius = 60; break; case “N”: //myColor = “colors/lightblue.jpg”; myColor = 0x8F8FFF; radius = 65; break; . . . Rest of the Chemical Elements go here, see the book’s source code . . . default: //myColor = “colors/deeppink.jpg”; myColor = 0xF71994; radius = 120; break; } //Draws the element’s billboard uses CURVE_TO and drawPath methods radius=radius/5; commands.push(GraphicsPathCommand.MOVE_TO); data.push(-radius/1.9, radius/1.9); data.push(0, radius); data.push(radius/1.9, radius/1.9); data.push(radius, 0); data.push(radius/1.9, -radius/1.9); data.push(0, -radius); data.push(-radius/1.9, -radius/1.9); data.push(-radius, 0); data.push(-radius/1.9, radius/1.9);
614
Chapter 16: Flash & Math: Exploring Flash 10 and Beyond commands.push(GraphicsPathCommand.CURVE_TO); commands.push(GraphicsPathCommand.CURVE_TO); commands.push(GraphicsPathCommand.CURVE_TO); commands.push(GraphicsPathCommand.CURVE_TO); this.graphics.beginGradientFill(myType, graphics.drawPath(commands, data);
[myColor, 0x000022],[1,1],
[1, 40]);
}}}
The Atom class only takes one parameter – a string that comes from the Molfile – giving it an atom type. So after the Molfile is parsed, atoms are created by iterating over all your atoms in your molecule and passing the appropriate atom type to your Atom class as shown in the following code snippet: for(var j:int = 0; j < myNumAtoms; j++) { //Instantiate your atom and pass in its assignment string var atom:Atom = new Atom(myArray[myStartVert+4 +j*16]); //Treat your atoms as particles by pushing them into an atoms array atoms.push(atom); marks.push(atom); //Position your atoms atom.xpos = myArray[myStartVert+1 +j*16] * myScale-offsetx; atom.ypos = myArray[myStartVert+2 +j*16] * myScale-offsety; atom.zpos = myArray[myStartVert+3 +j*16] * myScale-offsetz; //Place your atoms in their molecule holder myHolder.addChild(atom); }
Now that your atom sub-class is completed, it’s time to start building molecules.
Creating Molecules and Bonds There are two parts to creating molecules: placing atoms in 3D, and creating their bonds. The first part of a Molfile gives you the atom position values, and the second part gives the bond relationships (which atoms are connected to which) and types.
Placing Atoms Essentially, all you have to do is replace the random placement of circles from the post on drawPaths to the atomic positions found in your Molfile: atom.xpos = myArray[myStartVert+1 +j*16] * myScale - offsetx; atom.ypos = myArray[myStartVert+2 +j*16] * myScale - offsety; atom.zpos = myArray[myStartVert+3 +j*16] * myScale - offsetz; myHolder.addChild(atom);
You get to those positions by counting one position forward for x, two for y, and three for z, from the version number index, as shown above.
615
Part IV: Extending PV3D and Beyond The offset is just an average of the molecular positions and gives you the ability to spin your molecule around its center. Everything else pretty much follows what was done in the drawPaths post.
Creating Chemical Bonds Creating bonds is easy as well, with one big conceptual change from the drawPaths post. Double processing is eliminated by just duplicating the atom and placing it into a marks array. The lines follow the duplicated atom in the marks array and the other atom which exists in the atoms array is used for sorting. atoms.push(atom); marks.push(atom);
The big problem in creating bonds is figuring out how to create double, triple, and aromatic bonds. It turns out to be just a big counting game, coupled with offsetting lines. It starts with figuring out what type of bond you have and using that information in a switch statement. The information for the type of bond you have is carried in the second part of your Molfile which starts at: startBondArray=myStartVert+myNumAtoms*16+1;
Adding two to this number gives you the bond type location (OK once again it’s a big counting game – check out the Molfile description under the read more button to see the Molfile structure). So, each time you create a double, triple, or aromatic bond you have to keep track of where all the data is in your array. This was accomplished by adding the following counting tracker to your command and data arrays: commands[2*k+2*dB+4*tB+2*aB] mydata[4*k+4*dB+8*tB+4*aB]
This is needed for the drawPath command that’s shown here: myHolder.graphics.drawPath(commands, mydata);
The variables dB, tB, and aB are iterated by one each time you create a double, triple, or aromatic bond respectively. These values are then zeroed after each molecular drawing iteration and the process is restarted on each onEnterFrame tick. Creating the bond offsets isn’t very sophisticated as shown here: mydata[4*k+4*dB+8*tB+4*aB] = marks[myArray[startBondArray+7*k]-1].x-bond2Sep; mydata[4*k+1+4*dB+8*tB+4*aB] = marks[myArray[startBondArray+7*k]-1].y-bond2Sep; mydata[4*k+4+4*dB+8*tB+4*aB] = marks[myArray[startBondArray+7*k]-1].x+bond2Sep; mydata[4*k+5+4*dB+8*tB+4*aB] = marks[myArray[startBondArray+7*k]-1].y+bond2Sep;
You just subtract or add an offset value (bond2Sep) as shown above for the double bond case. The complete code can be downloaded from the book’s website. The next level for code developed for a project like this is to incorporate chemical bonding information and a molecular version of Jiglib so your
616
Chapter 16: Flash & Math: Exploring Flash 10 and Beyond molecules can interact and bond. The source code for this project can be downloaded from the book’s website. If you love mathematics and mapping you’ll love this next section. It’s all about mapping.
Mapping a Single Image onto an Octahedron Mapping a sphere to a cube becomes a fundamental issue in CS4, where vertex to UV data is a one-toone map. Typical UV data brought in from Blender or 3DSMax, which have multiple uv data points per vertex (or triangle fan), won’t work with the drawTriangles method of CS4 (unless you do complete triangle mapping). There are workarounds, but eventually it all boils down to creating some type of oneto-one mapping scheme. In this section you use the polar parametric equations of an octahedron to map a single image onto its surface, as shown in Figure 16.13. You won’t need to break your image up into eight separate sides.
Figure 16-13 To accomplish this mapping you first need to derive the octahedron parametric equations.
Polar Octahedron Derivation The generalized parametric mapping equations for the octahedron are simple, and for the 2D case boil down to: x = r()*cos() y = r()*sin()
and in 3D: x = r(, Φ)*cos()sin(Φ) y = r(, Φ)*sin()sin(Φ) z = r(, Φ)*cos(Φ)
where r is function of angle (not constant as in the spherical case).
617
Part IV: Extending PV3D and Beyond Solving for the 2D case, r() is given by: : : : :
0 to 90, r() = 1/(sin() + cos()) 90 to 180, r() = 1/(sin() - cos()) 180 to 270, r() = -1/(sin() + cos()) 270 to360, r() = -1/(sin() - cos())
These solutions are easily obtained by substituting x = r()*cos(), and y = r()*sin() into the linear equations (see Figure 16.14) and solving for r().
y X1
y X1 theta
y X1
y X1
Figure 16-14
The generalized solution is: r(a, b, ) = a/(sin() + b*cos())
where : 0 to 90 (a=1, b=1), : 90 to 180 (a=1, b=-1), : 180 to 270 (a=-1, b=-1), : 270 to360 (a=-1, b=-1)
Interestingly, the values of the a and b parameters correspond to all possible combinations of 1 and -1, which is similar to tossing two coins which gives: (HH, HT, TH, TT) where H=1, and T=-1.
3D Case Following the approach above, for the 3D case (where eight possible planes correspond to the eight sides of the polar cube), the general solution is given by: r(a, b, c, , Φ) = a/(cos(Φ)+b*sin()sin(Φ)+c*cos()sin(Φ))
where the parameters a, b, c correspond to flipping three coins at the same time with eight possible outcomes:
618
Chapter 16: Flash & Math: Exploring Flash 10 and Beyond (HHH, HHT, HTH, HTT, THH, THT, TTH, TTT or 111, 11-1, 1-11, 1-1-1, -111,- 11-1, -111, -1-1-1)
corresponding to the eight sides of our polar cube. The eight possibilities used to map the vertices of the cube are: : : : : : : : :
0 to 90, Φ:0 to 90 (-1,-1,-1) 90 to 180, Φ:0 to 90 (-1,-1,1) 180 to 270, Φ:0 to 90 (-1,1,1) 270 to360, Φ:0 to 90 (-1,1,-1) 0 to 90, Φ:90 to 180 (1,1,1) 90 to 180, Φ:90 to 180 (1,1,-1) 180 to 270, Φ:90 to 180 (1,-1,-1) 270 to360, Φ:90 to 180 (1,-1,1)
Using these possibilities you can now code your parametric equations.
Coding the Parametric Equations The coding is straightforward. The cube’s vertices are derived from the parametric form given above and its indices are derived from a one-to-one mapping of a sphere onto a cube. That’s why the spherically mapped image, shown in the image above, lies on the cube without distortion. for (var i:int = 0; i!=rows; i++) { ix= i/(rows-1)*Math.PI*2.0; for (var j:int = 0; j!=cols; j++) { iy= (j/(cols-1)-0.5)*Math.PI; // 8 planes 8 cases if(ix>=0 && ix=-2*Math.PI/4 && iy=2*Math.PI/4 && ix=-2*Math.PI/4 && iy=2*Math.PI/2 && ix=-2*Math.PI/4 && iy=6*Math.PI/4 && ix=-2*Math.PI/4 && iy0) { spSphere.removeChildAt(0); } for(i=0;i
Center a Panel startPanel.x=stateCanvas.width/2-startPanel.width/2; startPanel.y=stateCanvas.height/2-startPanel.height;
Set ViewState (currentState) //Move to Play State, Initiate PV3D, and Start Music private function cancelClickHandler(event:MouseEvent):void { //Go to playState currentState=”playState”; initPV3D(); myGameState(1); }
666
Appendix A: Pocket Reference
Custom Skybox package org.lively3d.jiglib { //PV3D import statements import org.papervision3d.materials.BitmapMaterial; import org.papervision3d.materials.utils.MaterialsList; import org.papervision3d.objects.primitives.Cube; //Public class extends the cube public class Skybox extends Cube { //Embed your image assets [Embed (source=”/./assets/skybox/top01.jpg”)] private var BMTop: Class; [Embed (source=”/./assets/skybox/side10.jpg”)] private var BMBottom: Class; [Embed (source=”/./assets/skybox/side08.jpg”)] private var BMFront: Class; [Embed (source=”/./assets/skybox/bottom02.jpg”)] private var BMBack: Class; [Embed (source=”/./assets/skybox/side09.jpg”)] private var BMRight: Class; [Embed (source=”/./assets/skybox/top02.jpg”)] private var BMLeft: Class; //Skybox Constructor public function Skybox() {var bmTop: BitmapMaterial = new BitmapMaterial(new BMTop().bitmapData); var bmBottom: BitmapMaterial = new BitmapMaterial(new BMBottom().bitmapData); var bmFront: BitmapMaterial = new BitmapMaterial(new BMFront().bitmapData); var bmBack: BitmapMaterial = new BitmapMaterial(new BMBack().bitmapData); var bmRight: BitmapMaterial = new BitmapMaterial(new BMRight().bitmapData); var bmLeft: BitmapMaterial = new BitmapMaterial(new BMLeft().bitmapData); //Set your material to double side so you’ll see the inside of your skybox bmTop.doubleSided = true; bmBottom.doubleSided = true; bmFront.doubleSided = true; bmBack.doubleSided = true; bmRight.doubleSided = true; bmLeft.doubleSided = true; //Create your material list var materials: MaterialsList = new MaterialsList(); //Add Your Materials to your Materials list materials.addMaterial(bmTop, “top”); materials.addMaterial(bmBottom, “bottom”); materials.addMaterial(bmFront, “front”); materials.addMaterial(bmBack, “back”); materials.addMaterial(bmRight, “right”); materials.addMaterial(bmLeft, “left”); // Add your materials, large sides, and low segments to your super class super(materials,10000,10000,10000,8,8,8); }}}
667
Appendix A: Pocket Reference
Jiglib Bounding Box public function createBoundingBox(material:MaterialObject3D, myWidth:Number, myDepth:Number, myHeight:Number, myX:Number=0, myY:Number=0, myZ:Number=0):void{ //Top Bottom createPlane(material, myWidth, myDepth, new Number3D(myX,myHeight/2+myY,myZ), new Number3D(-90,0,0)); createPlane(material, myWidth, myDepth, new Number3D(myX,-myHeight/2+myY,myZ), new Number3D(90,0,0)); //Left Right createPlane(material, myHeight, myWidth, new Number3D(myX,myY,-myDepth/2+myZ), new Number3D(0,180,90)); createPlane(material, myHeight, myWidth, new Number3D(myX,myY,myDepth/2+myZ), new Number3D(180,180,270)); //Front Back createPlane(material, myDepth, myHeight, new Number3D(myWidth/2+myX,myY,myZ), new Number3D(180,270,0)); createPlane(material, myDepth, myHeight, new Number3D(-myWidth/2+myX,myY,myZ), new Number3D(180,90,0)); }
Draw a Bullet Hole //draw the bullet hole var mat:MovieMaterial = event.renderHitData.material as MovieMaterial; var hole: Bitmap = Sprite(mat.movie).addChild(new bulletAsset()) as Bitmap; hole.blendMode = “overlay”; hole.x = event.renderHitData.u-hole.width*0.5; hole.y = event.renderHitData.v-hole.height*0.5; //Use drawBitmap to draw the hole mat.drawBitmap();
Rack Balls private function rackBalls():void{ //Iterate over your racking sphere for (var i:int = 0; i < myNum; i++) { jBall[i].moveTo(new JNumber3D(rackSphere.geometry.vertices[i].x, rackSphere. geometry.vertices[i].y, rackSphere.geometry.vertices[i].z)); // start position jBall[i].hitNumber=0; jBall[i].movable=true; }}
Stop Game Method private function showTime():void{ gameTime=getTimer()-getStartTime; if(gameTime>60000||numHitSank==10){ //Remove processes tickTimer=false; removeSight(); yesShoot=false;
668
Appendix A: Pocket Reference numHitSank=0; if(myScore