1 /* 2 3 Dynamic tree generation and placement in a night-time scene 4 5 Demonstrates: 6 7 How to create a height map and splat map from scratch to use for realistic terrain 8 How to use fratacl algorithms to create a custom tree-generating geometry primitive 9 How to save GPU memory by cloning complex. 10 11 Code by Rob Bateman & Alejadro Santander 12 rob@infiniteturtles.co.uk 13 http://www.infiniteturtles.co.uk 14 Alejandro Santander 15 http://www.lidev.com.ar/ 16 17 This code is distributed under the MIT License 18 19 Copyright (c) The Away Foundation http://www.theawayfoundation.org 20 21 Permission is hereby granted, free of charge, to any person obtaining a copy 22 of this software and associated documentation files (the “Software”), to deal 23 in the Software without restriction, including without limitation the rights 24 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 copies of the Software, and to permit persons to whom the Software is 26 furnished to do so, subject to the following conditions: 27 28 The above copyright notice and this permission notice shall be included in 29 all copies or substantial portions of the Software. 30 31 THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 37 THE SOFTWARE. 38 39 */ 40 41 package 42 { 43 44 import away3d.cameras.*; 45 import away3d.containers.*; 46 import away3d.controllers.*; 47 import away3d.debug.*; 48 import away3d.entities.*; 49 import away3d.extrusions.*; 50 import away3d.lights.*; 51 import away3d.materials.*; 52 import away3d.materials.lightpickers.*; 53 import away3d.materials.methods.*; 54 import away3d.primitives.*; 55 import away3d.textures.*; 56 import away3d.utils.*; 57 58 import com.bit101.components.Label; 59 60 import flash.display.*; 61 import flash.events.*; 62 import flash.filters.*; 63 import flash.geom.*; 64 import flash.ui.*; 65 import flash.utils.*; 66 67 import uk.co.soulwire.gui.*; 68 69 70 [SWF(backgroundColor="#000000", frameRate="30", quality="LOW")] 71 72 public class Advanced_FractalTreeDemo extends Sprite 73 { 74 //signature swf 75 [Embed(source="/../embeds/signature.swf", symbol="Signature")] 76 public var SignatureSwf:Class; 77 78 //skybox 79 [Embed(source="/../embeds/skybox/grimnight_posX.png")] 80 private var EnvPosX:Class; 81 [Embed(source="/../embeds/skybox/grimnight_posY.png")] 82 private var EnvPosY:Class; 83 [Embed(source="/../embeds/skybox/grimnight_posZ.png")] 84 private var EnvPosZ:Class; 85 [Embed(source="/../embeds/skybox/grimnight_negX.png")] 86 private var EnvNegX:Class; 87 [Embed(source="/../embeds/skybox/grimnight_negY.png")] 88 private var EnvNegY:Class; 89 [Embed(source="/../embeds/skybox/grimnight_negZ.png")] 90 private var EnvNegZ:Class; 91 92 //tree diffuse map 93 [Embed (source="../embeds/tree/bark0.jpg")] 94 public var TrunkDiffuse:Class; 95 96 //tree normal map 97 [Embed (source="../embeds/tree/barkNRM.png")] 98 public var TrunkNormals:Class; 99 100 //tree specular map 101 [Embed (source="../embeds/tree/barkSPEC.png")] 102 public var TrunkSpecular:Class; 103 104 //leaf diffuse map 105 [Embed (source="../embeds/tree/leaf4.jpg")] 106 public var LeafDiffuse:Class; 107 108 //splat texture maps 109 [Embed(source="/../embeds/terrain/grass.jpg")] 110 private var Grass:Class; 111 [Embed(source="/../embeds/terrain/rock.jpg")] 112 private var Rock:Class; 113 114 //engine variables 115 private var scene:Scene3D; 116 private var camera:Camera3D; 117 private var view:View3D; 118 private var awayStats:AwayStats; 119 private var cameraController:HoverController; 120 121 //signature variables 122 private var Signature:Sprite; 123 private var SignatureBitmap:Bitmap; 124 125 //light objects 126 private var moonLight:DirectionalLight; 127 private var cameraLight:PointLight; 128 private var skyLight:DirectionalLight; 129 private var lightPicker:StaticLightPicker; 130 private var fogMethod:FogMethod; 131 132 //material objects 133 private var heightMapData:BitmapData; 134 private var blendBitmapData:BitmapData; 135 private var destPoint:Point = new Point(); 136 private var blendTexture:BitmapTexture; 137 private var terrainMethod:TerrainDiffuseMethod; 138 private var terrainMaterial:TextureMaterial; 139 private var trunkMaterial:TextureMaterial; 140 private var leafMaterial:TextureMaterial; 141 private var cubeTexture:BitmapCubeTexture; 142 143 //scene objects 144 private var terrain:Elevation; 145 private var tree:Mesh; 146 private var foliage:Mesh; 147 private var gui:SimpleGUI; 148 149 //navigation variables 150 private var move:Boolean = false; 151 private var lastPanAngle:Number; 152 private var lastTiltAngle:Number; 153 private var lastMouseX:Number; 154 private var lastMouseY:Number; 155 private var tiltSpeed:Number = 2; 156 private var panSpeed:Number = 2; 157 private var distanceSpeed:Number = 1000; 158 private var tiltIncrement:Number = 0; 159 private var panIncrement:Number = 0; 160 private var distanceIncrement:Number = 0; 161 162 //gui objects 163 private var treeCountLabel:Label; 164 private var polyCountLabel:Label; 165 private var terrainPolyCountLabel:Label; 166 private var treePolyCountLabel:Label; 167 168 //tree configuration variables 169 private var treeLevel:uint = 10; 170 private var treeCount:uint = 25; 171 private var treeTimer:Timer; 172 private var treeDelay:uint = 0; 173 private var treeSize:Number = 1000; 174 private var treeMin:Number = 0.75; 175 private var treeMax:Number = 1.25; 176 177 //foliage configuration variables 178 private var leafSize:Number = 300; 179 private var leavesPerCluster:uint = 5; 180 private var leafClusterRadius:Number = 400; 181 182 //terrain configuration variables 183 private var terrainY:Number = -10000; 184 private var terrainWidth:Number = 200000; 185 private var terrainHeight:Number = 50000; 186 private var terrainDepth:Number = 200000; 187 188 private var currentTreeCount:uint; 189 private var polyCount:uint; 190 private var terrainPolyCount:uint; 191 private var treePolyCount:uint; 192 private var clonesCreated:Boolean; 193 194 public var minAperture:Number = 0.4; 195 public var maxAperture:Number = 0.5; 196 public var minTwist:Number = 0.3; 197 public var maxTwist:Number = 0.6; 198 199 /** 200 * Constructor 201 */ 202 public function Advanced_FractalTreeDemo() 203 { 204 init(); 205 } 206 207 /** 208 * Global initialise function 209 */ 210 private function init():void 211 { 212 initEngine(); 213 initLights(); 214 initMaterials(); 215 initObjects(); 216 initGUI(); 217 initListeners(); 218 } 219 220 /** 221 * Initialise the engine 222 */ 223 private function initEngine():void 224 { 225 stage.scaleMode = StageScaleMode.NO_SCALE; 226 stage.align = StageAlign.TOP_LEFT; 227 228 view = new View3D(); 229 scene = view.scene; 230 camera = view.camera; 231 camera.lens.far = 1000000; 232 233 //setup controller to be used on the camera 234 cameraController = new HoverController(camera, null, 0, 10, 25000, 0, 70); 235 236 view.addSourceURL("srcview/index.html"); 237 addChild(view); 238 239 //add signature 240 Signature = Sprite(new SignatureSwf()); 241 SignatureBitmap = new Bitmap(new BitmapData(Signature.width, Signature.height, true, 0)); 242 stage.quality = StageQuality.HIGH; 243 SignatureBitmap.bitmapData.draw(Signature); 244 stage.quality = StageQuality.LOW; 245 addChild(SignatureBitmap); 246 247 awayStats = new AwayStats(view); 248 addChild(awayStats); 249 } 250 251 /** 252 * Initialise the lights 253 */ 254 private function initLights():void 255 { 256 moonLight = new DirectionalLight(); 257 moonLight.position = new Vector3D(3500, 4500, 10000); // Appear to come from the moon in the sky box. 258 moonLight.lookAt(new Vector3D(0, 0, 0)); 259 moonLight.diffuse = 0.5; 260 moonLight.specular = 0.25; 261 moonLight.color = 0xFFFFFF; 262 scene.addChild(moonLight); 263 cameraLight = new PointLight(); 264 cameraLight.diffuse = 0.25; 265 cameraLight.specular = 0.25; 266 cameraLight.color = 0xFFFFFF; 267 cameraLight.radius = 1000; 268 cameraLight.fallOff = 2000; 269 scene.addChild(cameraLight); 270 skyLight = new DirectionalLight(); 271 skyLight.diffuse = 0.1; 272 skyLight.specular = 0.1; 273 skyLight.color = 0xFFFFFF; 274 scene.addChild(skyLight); 275 276 lightPicker = new StaticLightPicker([moonLight, cameraLight, skyLight]); 277 278 //create a global fog method 279 fogMethod = new FogMethod(0, 200000, 0x000000); 280 } 281 282 /** 283 * Initialise the material 284 */ 285 private function initMaterials():void 286 { 287 //create skybox texture 288 cubeTexture = new BitmapCubeTexture(Cast.bitmapData(EnvPosX), Cast.bitmapData(EnvNegX), Cast.bitmapData(EnvPosY), Cast.bitmapData(EnvNegY), Cast.bitmapData(EnvPosZ), Cast.bitmapData(EnvNegZ)); 289 290 //create tree material 291 trunkMaterial = new TextureMaterial(Cast.bitmapTexture(TrunkDiffuse)); 292 trunkMaterial.normalMap = Cast.bitmapTexture(TrunkNormals); 293 trunkMaterial.specularMap = Cast.bitmapTexture(TrunkSpecular); 294 trunkMaterial.diffuseMethod = new BasicDiffuseMethod(); 295 trunkMaterial.specularMethod = new BasicSpecularMethod(); 296 trunkMaterial.addMethod(fogMethod); 297 trunkMaterial.lightPicker = lightPicker; 298 299 //create leaf material 300 leafMaterial = new TextureMaterial(Cast.bitmapTexture(LeafDiffuse)); 301 leafMaterial.addMethod(fogMethod); 302 leafMaterial.lightPicker = lightPicker; 303 304 //create height map 305 heightMapData = new BitmapData(512, 512, false, 0x0); 306 heightMapData.perlinNoise(200, 200, 4, uint(1000*Math.random()), false, true, 7, true); 307 heightMapData.draw(createGradientSprite(512, 512, 1, 0)); 308 309 //create terrain diffuse method 310 blendBitmapData = new BitmapData(heightMapData.width, heightMapData.height, false, 0x000000); 311 blendBitmapData.threshold(heightMapData, blendBitmapData.rect, destPoint, ">", 0x444444, 0xFFFF0000, 0xFFFFFF, false); 312 blendBitmapData.applyFilter(blendBitmapData, blendBitmapData.rect, destPoint, new BlurFilter(16, 16, 3)); 313 blendTexture = new BitmapTexture(blendBitmapData); 314 terrainMethod = new TerrainDiffuseMethod([Cast.bitmapTexture(Rock)], blendTexture, [20, 20]); 315 316 //create terrain material 317 terrainMaterial = new TextureMaterial(Cast.bitmapTexture(Grass)); 318 terrainMaterial.diffuseMethod = terrainMethod; 319 terrainMaterial.addMethod(new FogMethod(0, 200000, 0x000000)); //TODO: global fog method affects splats when updated 320 terrainMaterial.lightPicker = lightPicker; 321 } 322 323 /** 324 * Initialise the scene objects 325 */ 326 private function initObjects():void 327 { 328 //create skybox. 329 scene.addChild(new SkyBox(cubeTexture)); 330 331 332 333 //create terrain 334 terrain = new Elevation(terrainMaterial, heightMapData, terrainWidth, terrainHeight, terrainDepth, 65, 65); 335 terrain.y = terrainY; 336 //terrain.smoothHeightMap(); 337 scene.addChild(terrain); 338 339 terrainPolyCount = terrain.geometry.subGeometries[0].vertexData.length/3; 340 polyCount += terrainPolyCount; 341 } 342 343 /** 344 * Initialise the GUI 345 */ 346 private function initGUI():void 347 { 348 gui = new SimpleGUI(this); 349 350 gui.addColumn("Instructions"); 351 var instr:String = "Click and drag to rotate camera. "; 352 instr += "Arrows and WASD also rotate camera. "; 353 instr += "Z and X zoom camera. "; 354 instr += "Create a tree, then clone it to "; 355 instr += "populate the terrain with trees. "; 356 gui.addLabel(instr); 357 gui.addColumn("Tree"); 358 gui.addSlider("minAperture", 0, 1, {label:"min aperture", tick:0.01}); 359 gui.addSlider("maxAperture", 0, 1, {label:"max aperture", tick:0.01}); 360 gui.addSlider("minTwist", 0, 1, {label:"min twist", tick:0.01}); 361 gui.addSlider("maxTwist", 0, 1, {label:"max twist", tick:0.01}); 362 gui.addButton("Generate Fractal Tree", {callback:generateTree, 160}); 363 gui.addColumn("Forest"); 364 gui.addButton("Clone!", {callback:generateClones}); 365 treeCountLabel = gui.addControl(Label, {text:"trees: 0"}) as Label; 366 polyCountLabel = gui.addControl(Label, {text:"polys: 0"}) as Label; 367 treePolyCountLabel = gui.addControl(Label, {text:"polys/tree: 0"}) as Label; 368 terrainPolyCountLabel = gui.addControl(Label, {text:"polys/terrain: 0"}) as Label; 369 gui.show(); 370 371 updateLabels(); 372 } 373 374 /** 375 * Initialise the listeners 376 */ 377 private function initListeners():void 378 { 379 380 addEventListener(Event.ENTER_FRAME, onEnterFrame); 381 view.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); 382 view.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); 383 stage.addEventListener(Event.RESIZE, onResize); 384 stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); 385 stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp); 386 onResize(); 387 } 388 389 public function generateTree():void 390 { 391 if(tree) { 392 currentTreeCount--; 393 scene.removeChild(tree); 394 tree = null; 395 } 396 397 if(foliage) { 398 scene.removeChild(foliage); 399 foliage = null; 400 } 401 402 createTreeShadow(0, 0); 403 404 // Create tree. 405 var treeGeometry:FractalTreeRound = new FractalTreeRound(treeSize, 10, 3, minAperture, maxAperture, minTwist, maxTwist, treeLevel); 406 tree = new Mesh(treeGeometry, trunkMaterial); 407 tree.rotationY = 360*Math.random(); 408 tree.y = terrain != null ? terrain.y + terrain.getHeightAt(tree.x, tree.z) : 0; 409 scene.addChild(tree); 410 411 // Create tree leaves. 412 foliage = new Mesh(new Foliage(treeGeometry.leafPositions, leavesPerCluster, leafSize, leafClusterRadius), leafMaterial); 413 foliage.x = tree.x; 414 foliage.y = tree.y; 415 foliage.z = tree.z; 416 foliage.rotationY = tree.rotationY; 417 scene.addChild(foliage); 418 419 // Count. 420 currentTreeCount++; 421 treePolyCount = tree.geometry.subGeometries[0].vertexData.length/3 + foliage.geometry.subGeometries[0].vertexData.length/3; 422 polyCount += treePolyCount; 423 updateLabels(); 424 } 425 426 public function generateClones():void 427 { 428 if(!tree || clonesCreated) 429 return; 430 431 // Start tree creation. 432 if(treeCount > 0) { 433 treeTimer = new Timer(treeDelay, treeCount - 1); 434 treeTimer.addEventListener(TimerEvent.TIMER, onTreeTimer); 435 treeTimer.start(); 436 } 437 438 clonesCreated = true; 439 } 440 441 private function createTreeShadow(x:Number, z:Number):void 442 { 443 // Paint on the terrain's shadow blend layer 444 var matrix:Matrix = new Matrix(); 445 var dx:Number = (x/terrainWidth + 0.5)*512 - 8; 446 var dy:Number = (-z/terrainDepth + 0.5)*512 - 8; 447 matrix.translate(dx, dy); 448 var treeShadowBitmapData:BitmapData = new BitmapData(16, 16, false, 0x0000FF); 449 treeShadowBitmapData.draw(createGradientSprite(16, 16, 0, 1), matrix); 450 blendBitmapData.draw(treeShadowBitmapData, matrix, null, BlendMode.ADD); 451 452 // Update the terrain. 453 blendTexture.bitmapData = blendBitmapData; // TODO: invalidation routine not active for blending texture 454 } 455 456 private function createGradientSprite(Number, height:Number, alpha1:Number, alpha2:Number):Sprite 457 { 458 var gradSpr:Sprite = new Sprite(); 459 var matrix:Matrix = new Matrix(); 460 matrix.createGradientBox(width, height, 0, 0, 0); 461 gradSpr.graphics.beginGradientFill(GradientType.RADIAL, [0xFF000000, 0xFF000000], [alpha1, alpha2], [0, 255], matrix); 462 gradSpr.graphics.drawRect(0, 0, width, height); 463 gradSpr.graphics.endFill(); 464 return gradSpr; 465 } 466 467 private function updateLabels():void 468 { 469 treeCountLabel.text = "trees: " + currentTreeCount; 470 polyCountLabel.text = "polys: " + polyCount; 471 treePolyCountLabel.text = "polys/tree: " + treePolyCount; 472 terrainPolyCountLabel.text = "polys/terrain: " + terrainPolyCount; 473 } 474 475 /** 476 * Navigation and render loop 477 */ 478 private function onEnterFrame(event:Event):void 479 { 480 if (move) { 481 cameraController.panAngle = 0.3*(stage.mouseX - lastMouseX) + lastPanAngle; 482 cameraController.tiltAngle = 0.3*(stage.mouseY - lastMouseY) + lastTiltAngle; 483 } 484 485 cameraController.panAngle += panIncrement; 486 cameraController.tiltAngle += tiltIncrement; 487 cameraController.distance += distanceIncrement; 488 489 // Update light. 490 cameraLight.transform = camera.transform.clone(); 491 492 view.render(); 493 } 494 495 /** 496 * Key down listener for camera control 497 */ 498 private function onKeyDown(event:KeyboardEvent):void 499 { 500 switch (event.keyCode) { 501 case Keyboard.UP: 502 case Keyboard.W: 503 tiltIncrement = tiltSpeed; 504 break; 505 case Keyboard.DOWN: 506 case Keyboard.S: 507 tiltIncrement = -tiltSpeed; 508 break; 509 case Keyboard.LEFT: 510 case Keyboard.A: 511 panIncrement = panSpeed; 512 break; 513 case Keyboard.RIGHT: 514 case Keyboard.D: 515 panIncrement = -panSpeed; 516 break; 517 case Keyboard.Z: 518 distanceIncrement = distanceSpeed; 519 break; 520 case Keyboard.X: 521 distanceIncrement = -distanceSpeed; 522 break; 523 } 524 } 525 526 /** 527 * Key up listener for camera control 528 */ 529 private function onKeyUp(event:KeyboardEvent):void 530 { 531 switch (event.keyCode) { 532 case Keyboard.UP: 533 case Keyboard.W: 534 case Keyboard.DOWN: 535 case Keyboard.S: 536 tiltIncrement = 0; 537 break; 538 case Keyboard.LEFT: 539 case Keyboard.A: 540 case Keyboard.RIGHT: 541 case Keyboard.D: 542 panIncrement = 0; 543 break; 544 case Keyboard.Z: 545 case Keyboard.X: 546 distanceIncrement = 0; 547 break; 548 } 549 } 550 551 /** 552 * Mouse down listener for navigation 553 */ 554 private function onMouseDown(event:MouseEvent):void 555 { 556 move = true; 557 lastPanAngle = cameraController.panAngle; 558 lastTiltAngle = cameraController.tiltAngle; 559 lastMouseX = stage.mouseX; 560 lastMouseY = stage.mouseY; 561 stage.addEventListener(Event.MOUSE_LEAVE, onStageMouseLeave); 562 } 563 564 /** 565 * Mouse up listener for navigation 566 */ 567 private function onMouseUp(event:MouseEvent):void 568 { 569 move = false; 570 stage.removeEventListener(Event.MOUSE_LEAVE, onStageMouseLeave); 571 } 572 573 /** 574 * Mouse stage leave listener for navigation 575 */ 576 private function onStageMouseLeave(event:Event):void 577 { 578 move = false; 579 stage.removeEventListener(Event.MOUSE_LEAVE, onStageMouseLeave); 580 } 581 582 /** 583 * stage listener for resize events 584 */ 585 private function onResize(event:Event = null):void 586 { 587 view.width = stage.stageWidth; 588 view.height = stage.stageHeight; 589 SignatureBitmap.y = stage.stageHeight - Signature.height; 590 awayStats.x = stage.stageWidth - awayStats.width; 591 } 592 593 /** 594 * stage listener for resize events 595 */ 596 private function onTreeTimer(event:TimerEvent):void 597 { 598 //create tree clone. 599 var treeClone:Mesh = tree.clone() as Mesh; 600 treeClone.x = terrainWidth*Math.random() - terrainWidth/2; 601 treeClone.z = terrainDepth*Math.random() - terrainDepth/2; 602 treeClone.y = terrain != null ? terrain.y + terrain.getHeightAt(treeClone.x, treeClone.z) : 0; 603 treeClone.rotationY = 360*Math.random(); 604 treeClone.scale((treeMax - treeMin)*Math.random() + treeMin); 605 scene.addChild(treeClone); 606 607 //create foliage clone. 608 var foliageClone:Mesh = foliage.clone() as Mesh; 609 foliageClone.x = treeClone.x; 610 foliageClone.y = treeClone.y; 611 foliageClone.z = treeClone.z; 612 foliageClone.rotationY = treeClone.rotationY; 613 foliageClone.scale(treeClone.scaleX); 614 scene.addChild(foliageClone); 615 616 //create tree shadow clone. 617 createTreeShadow(treeClone.x, treeClone.z); 618 619 //count. 620 currentTreeCount++; 621 polyCount += treePolyCount; 622 updateLabels(); 623 } 624 625 } 626 }
注:素材和实例可到官网下载。