1/* 2 * Copyright (c) 2009-2012 jMonkeyEngine 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33package com.jme3.terrain.geomipmap; 34 35import com.jme3.bounding.BoundingBox; 36import com.jme3.bounding.BoundingVolume; 37import com.jme3.collision.Collidable; 38import com.jme3.collision.CollisionResults; 39import com.jme3.export.InputCapsule; 40import com.jme3.export.JmeExporter; 41import com.jme3.export.JmeImporter; 42import com.jme3.export.OutputCapsule; 43import com.jme3.material.Material; 44import com.jme3.math.FastMath; 45import com.jme3.math.Ray; 46import com.jme3.math.Vector2f; 47import com.jme3.math.Vector3f; 48import com.jme3.scene.Geometry; 49import com.jme3.scene.Node; 50import com.jme3.scene.Spatial; 51import com.jme3.scene.debug.WireBox; 52import com.jme3.terrain.ProgressMonitor; 53import com.jme3.terrain.Terrain; 54import com.jme3.terrain.geomipmap.lodcalc.LodCalculator; 55import com.jme3.terrain.geomipmap.picking.BresenhamTerrainPicker; 56import com.jme3.terrain.geomipmap.picking.TerrainPickData; 57import com.jme3.terrain.geomipmap.picking.TerrainPicker; 58import com.jme3.util.TangentBinormalGenerator; 59import java.io.IOException; 60import java.util.ArrayList; 61import java.util.HashMap; 62import java.util.List; 63import java.util.Map; 64import java.util.concurrent.ExecutorService; 65import java.util.concurrent.Executors; 66import java.util.concurrent.ThreadFactory; 67import java.util.logging.Level; 68import java.util.logging.Logger; 69 70/** 71 * A terrain quad is a node in the quad tree of the terrain system. 72 * The root terrain quad will be the only one that receives the update() call every frame 73 * and it will determine if there has been any LOD change. 74 * 75 * The leaves of the terrain quad tree are Terrain Patches. These have the real geometry mesh. 76 * 77 * 78 * Heightmap coordinates start from the bottom left of the world and work towards the 79 * top right. 80 * 81 * +x 82 * ^ 83 * | ......N = length of heightmap 84 * | : : 85 * | : : 86 * | 0.....: 87 * +---------> +z 88 * (world coordinates) 89 * 90 * @author Brent Owens 91 */ 92public class TerrainQuad extends Node implements Terrain { 93 94 protected Vector2f offset; 95 96 protected int totalSize; // the size of this entire terrain tree (on one side) 97 98 protected int size; // size of this quad, can be between totalSize and patchSize 99 100 protected int patchSize; // size of the individual patches 101 102 protected Vector3f stepScale; 103 104 protected float offsetAmount; 105 106 protected int quadrant = 0; // 1=upper left, 2=lower left, 3=upper right, 4=lower right 107 108 //protected LodCalculatorFactory lodCalculatorFactory; 109 //protected LodCalculator lodCalculator; 110 111 protected List<Vector3f> lastCameraLocations; // used for LOD calc 112 private boolean lodCalcRunning = false; 113 private int lodOffCount = 0; 114 private int maxLod = -1; 115 private HashMap<String,UpdatedTerrainPatch> updatedPatches; 116 private final Object updatePatchesLock = new Object(); 117 private BoundingBox affectedAreaBBox; // only set in the root quad 118 119 private TerrainPicker picker; 120 private Vector3f lastScale = Vector3f.UNIT_XYZ; 121 122 protected ExecutorService executor; 123 124 protected ExecutorService createExecutorService() { 125 return Executors.newSingleThreadExecutor(new ThreadFactory() { 126 public Thread newThread(Runnable r) { 127 Thread th = new Thread(r); 128 th.setName("jME Terrain Thread"); 129 th.setDaemon(true); 130 return th; 131 } 132 }); 133 } 134 135 public TerrainQuad() { 136 super("Terrain"); 137 } 138 139 /** 140 * 141 * @param name the name of the scene element. This is required for 142 * identification and comparison purposes. 143 * @param patchSize size of the individual patches 144 * @param totalSize the size of this entire terrain tree (on one side) 145 * @param heightMap The height map to generate the terrain from (a flat 146 * height map will be generated if this is null) 147 */ 148 public TerrainQuad(String name, int patchSize, int totalSize, float[] heightMap) { 149 this(name, patchSize, totalSize, Vector3f.UNIT_XYZ, heightMap); 150 } 151 152 /** 153 * 154 * @param name the name of the scene element. This is required for 155 * identification and comparison purposes. 156 * @param patchSize size of the individual patches 157 * @param quadSize 158 * @param totalSize the size of this entire terrain tree (on one side) 159 * @param heightMap The height map to generate the terrain from (a flat 160 * height map will be generated if this is null) 161 */ 162 public TerrainQuad(String name, int patchSize, int quadSize, int totalSize, float[] heightMap) { 163 this(name, patchSize, totalSize, quadSize, Vector3f.UNIT_XYZ, heightMap); 164 } 165 166 /** 167 * 168 * @param name the name of the scene element. This is required for 169 * identification and comparison purposes. 170 * @param patchSize size of the individual patches 171 * @param size size of this quad, can be between totalSize and patchSize 172 * @param scale 173 * @param heightMap The height map to generate the terrain from (a flat 174 * height map will be generated if this is null) 175 */ 176 public TerrainQuad(String name, int patchSize, int size, Vector3f scale, float[] heightMap) { 177 this(name, patchSize, size, scale, heightMap, size, new Vector2f(), 0); 178 affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), size*2, Float.MAX_VALUE, size*2); 179 fixNormalEdges(affectedAreaBBox); 180 addControl(new NormalRecalcControl(this)); 181 } 182 183 /** 184 * 185 * @param name the name of the scene element. This is required for 186 * identification and comparison purposes. 187 * @param patchSize size of the individual patches 188 * @param totalSize the size of this entire terrain tree (on one side) 189 * @param quadSize 190 * @param scale 191 * @param heightMap The height map to generate the terrain from (a flat 192 * height map will be generated if this is null) 193 */ 194 public TerrainQuad(String name, int patchSize, int totalSize, int quadSize, Vector3f scale, float[] heightMap) { 195 this(name, patchSize, quadSize, scale, heightMap, totalSize, new Vector2f(), 0); 196 affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), totalSize*2, Float.MAX_VALUE, totalSize*2); 197 fixNormalEdges(affectedAreaBBox); 198 addControl(new NormalRecalcControl(this)); 199 } 200 201 protected TerrainQuad(String name, int patchSize, int quadSize, 202 Vector3f scale, float[] heightMap, int totalSize, 203 Vector2f offset, float offsetAmount) 204 { 205 super(name); 206 207 if (heightMap == null) 208 heightMap = generateDefaultHeightMap(quadSize); 209 210 if (!FastMath.isPowerOfTwo(quadSize - 1)) { 211 throw new RuntimeException("size given: " + quadSize + " Terrain quad sizes may only be (2^N + 1)"); 212 } 213 if (FastMath.sqrt(heightMap.length) > quadSize) { 214 Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Heightmap size is larger than the terrain size. Make sure your heightmap image is the same size as the terrain!"); 215 } 216 217 this.offset = offset; 218 this.offsetAmount = offsetAmount; 219 this.totalSize = totalSize; 220 this.size = quadSize; 221 this.patchSize = patchSize; 222 this.stepScale = scale; 223 //this.lodCalculatorFactory = lodCalculatorFactory; 224 //this.lodCalculator = lodCalculator; 225 split(patchSize, heightMap); 226 } 227 228 /*public void setLodCalculatorFactory(LodCalculatorFactory lodCalculatorFactory) { 229 if (children != null) { 230 for (int i = children.size(); --i >= 0;) { 231 Spatial child = children.get(i); 232 if (child instanceof TerrainQuad) { 233 ((TerrainQuad) child).setLodCalculatorFactory(lodCalculatorFactory); 234 } else if (child instanceof TerrainPatch) { 235 ((TerrainPatch) child).setLodCalculator(lodCalculatorFactory.createCalculator((TerrainPatch) child)); 236 } 237 } 238 } 239 }*/ 240 241 242 /** 243 * Create just a flat heightmap 244 */ 245 private float[] generateDefaultHeightMap(int size) { 246 float[] heightMap = new float[size*size]; 247 return heightMap; 248 } 249 250 /** 251 * Call from the update() method of a terrain controller to update 252 * the LOD values of each patch. 253 * This will perform the geometry calculation in a background thread and 254 * do the actual update on the opengl thread. 255 */ 256 public void update(List<Vector3f> locations, LodCalculator lodCalculator) { 257 updateLOD(locations, lodCalculator); 258 } 259 260 /** 261 * update the normals if there were any height changes recently. 262 * Should only be called on the root quad 263 */ 264 protected void updateNormals() { 265 266 if (needToRecalculateNormals()) { 267 //TODO background-thread this if it ends up being expensive 268 fixNormals(affectedAreaBBox); // the affected patches 269 fixNormalEdges(affectedAreaBBox); // the edges between the patches 270 271 setNormalRecalcNeeded(null); // set to false 272 } 273 } 274 275 // do all of the LOD calculations 276 protected void updateLOD(List<Vector3f> locations, LodCalculator lodCalculator) { 277 // update any existing ones that need updating 278 updateQuadLODs(); 279 280 if (lodCalculator.isLodOff()) { 281 // we want to calculate the base lod at least once 282 if (lodOffCount == 1) 283 return; 284 else 285 lodOffCount++; 286 } else 287 lodOffCount = 0; 288 289 if (lastCameraLocations != null) { 290 if (lastCameraLocationsTheSame(locations) && !lodCalculator.isLodOff()) 291 return; // don't update if in same spot 292 else 293 lastCameraLocations = cloneVectorList(locations); 294 } 295 else { 296 lastCameraLocations = cloneVectorList(locations); 297 return; 298 } 299 300 if (isLodCalcRunning()) { 301 return; 302 } 303 304 if (getParent() instanceof TerrainQuad) { 305 return; // we just want the root quad to perform this. 306 } 307 308 if (executor == null) 309 executor = createExecutorService(); 310 311 UpdateLOD updateLodThread = new UpdateLOD(locations, lodCalculator); 312 executor.execute(updateLodThread); 313 } 314 315 private synchronized boolean isLodCalcRunning() { 316 return lodCalcRunning; 317 } 318 319 private synchronized void setLodCalcRunning(boolean running) { 320 lodCalcRunning = running; 321 } 322 323 private List<Vector3f> cloneVectorList(List<Vector3f> locations) { 324 List<Vector3f> cloned = new ArrayList<Vector3f>(); 325 for(Vector3f l : locations) 326 cloned.add(l.clone()); 327 return cloned; 328 } 329 330 private boolean lastCameraLocationsTheSame(List<Vector3f> locations) { 331 boolean theSame = true; 332 for (Vector3f l : locations) { 333 for (Vector3f v : lastCameraLocations) { 334 if (!v.equals(l) ) { 335 theSame = false; 336 return false; 337 } 338 } 339 } 340 return theSame; 341 } 342 343 private int collideWithRay(Ray ray, CollisionResults results) { 344 if (picker == null) 345 picker = new BresenhamTerrainPicker(this); 346 347 Vector3f intersection = picker.getTerrainIntersection(ray, results); 348 if (intersection != null) 349 return 1; 350 else 351 return 0; 352 } 353 354 /** 355 * Generate the entropy values for the terrain for the "perspective" LOD 356 * calculator. This routine can take a long time to run! 357 * @param progressMonitor optional 358 */ 359 public void generateEntropy(ProgressMonitor progressMonitor) { 360 // only check this on the root quad 361 if (isRootQuad()) 362 if (progressMonitor != null) { 363 int numCalc = (totalSize-1)/(patchSize-1); // make it an even number 364 progressMonitor.setMonitorMax(numCalc*numCalc); 365 } 366 367 if (children != null) { 368 for (int i = children.size(); --i >= 0;) { 369 Spatial child = children.get(i); 370 if (child instanceof TerrainQuad) { 371 ((TerrainQuad) child).generateEntropy(progressMonitor); 372 } else if (child instanceof TerrainPatch) { 373 ((TerrainPatch) child).generateLodEntropies(); 374 if (progressMonitor != null) 375 progressMonitor.incrementProgress(1); 376 } 377 } 378 } 379 380 // only do this on the root quad 381 if (isRootQuad()) 382 if (progressMonitor != null) 383 progressMonitor.progressComplete(); 384 } 385 386 protected boolean isRootQuad() { 387 return (getParent() != null && !(getParent() instanceof TerrainQuad) ); 388 } 389 390 public Material getMaterial() { 391 return getMaterial(null); 392 } 393 394 public Material getMaterial(Vector3f worldLocation) { 395 // get the material from one of the children. They all share the same material 396 if (children != null) { 397 for (int i = children.size(); --i >= 0;) { 398 Spatial child = children.get(i); 399 if (child instanceof TerrainQuad) { 400 return ((TerrainQuad)child).getMaterial(worldLocation); 401 } else if (child instanceof TerrainPatch) { 402 return ((TerrainPatch)child).getMaterial(); 403 } 404 } 405 } 406 return null; 407 } 408 409 //public float getTextureCoordinateScale() { 410 // return 1f/(float)totalSize; 411 //} 412 public int getNumMajorSubdivisions() { 413 return 1; 414 } 415 416 /** 417 * Calculates the LOD of all child terrain patches. 418 */ 419 private class UpdateLOD implements Runnable { 420 private List<Vector3f> camLocations; 421 private LodCalculator lodCalculator; 422 423 UpdateLOD(List<Vector3f> camLocations, LodCalculator lodCalculator) { 424 this.camLocations = camLocations; 425 this.lodCalculator = lodCalculator; 426 } 427 428 public void run() { 429 long start = System.currentTimeMillis(); 430 if (isLodCalcRunning()) { 431 //System.out.println("thread already running"); 432 return; 433 } 434 //System.out.println("spawned thread "+toString()); 435 setLodCalcRunning(true); 436 437 // go through each patch and calculate its LOD based on camera distance 438 HashMap<String,UpdatedTerrainPatch> updated = new HashMap<String,UpdatedTerrainPatch>(); 439 boolean lodChanged = calculateLod(camLocations, updated, lodCalculator); // 'updated' gets populated here 440 441 if (!lodChanged) { 442 // not worth updating anything else since no one's LOD changed 443 setLodCalcRunning(false); 444 return; 445 } 446 // then calculate its neighbour LOD values for seaming in the shader 447 findNeighboursLod(updated); 448 449 fixEdges(updated); // 'updated' can get added to here 450 451 reIndexPages(updated, lodCalculator.usesVariableLod()); 452 453 setUpdateQuadLODs(updated); // set back to main ogl thread 454 455 setLodCalcRunning(false); 456 //double duration = (System.currentTimeMillis()-start); 457 //System.out.println("terminated in "+duration); 458 } 459 } 460 461 private void setUpdateQuadLODs(HashMap<String,UpdatedTerrainPatch> updated) { 462 synchronized (updatePatchesLock) { 463 updatedPatches = updated; 464 } 465 } 466 467 /** 468 * Back on the ogl thread: update the terrain patch geometries 469 * @param updatedPatches to be updated 470 */ 471 private void updateQuadLODs() { 472 synchronized (updatePatchesLock) { 473 474 if (updatedPatches == null || updatedPatches.isEmpty()) 475 return; 476 477 // do the actual geometry update here 478 for (UpdatedTerrainPatch utp : updatedPatches.values()) { 479 utp.updateAll(); 480 } 481 482 updatedPatches.clear(); 483 } 484 } 485 486 public boolean hasPatchesToUpdate() { 487 return updatedPatches != null && !updatedPatches.isEmpty(); 488 } 489 490 protected boolean calculateLod(List<Vector3f> location, HashMap<String,UpdatedTerrainPatch> updates, LodCalculator lodCalculator) { 491 492 boolean lodChanged = false; 493 494 if (children != null) { 495 for (int i = children.size(); --i >= 0;) { 496 Spatial child = children.get(i); 497 if (child instanceof TerrainQuad) { 498 boolean b = ((TerrainQuad) child).calculateLod(location, updates, lodCalculator); 499 if (b) 500 lodChanged = true; 501 } else if (child instanceof TerrainPatch) { 502 boolean b = lodCalculator.calculateLod((TerrainPatch) child, location, updates); 503 if (b) 504 lodChanged = true; 505 } 506 } 507 } 508 509 return lodChanged; 510 } 511 512 protected synchronized void findNeighboursLod(HashMap<String,UpdatedTerrainPatch> updated) { 513 if (children != null) { 514 for (int x = children.size(); --x >= 0;) { 515 Spatial child = children.get(x); 516 if (child instanceof TerrainQuad) { 517 ((TerrainQuad) child).findNeighboursLod(updated); 518 } else if (child instanceof TerrainPatch) { 519 520 TerrainPatch patch = (TerrainPatch) child; 521 if (!patch.searchedForNeighboursAlready) { 522 // set the references to the neighbours 523 patch.rightNeighbour = findRightPatch(patch); 524 patch.bottomNeighbour = findDownPatch(patch); 525 patch.leftNeighbour = findLeftPatch(patch); 526 patch.topNeighbour = findTopPatch(patch); 527 patch.searchedForNeighboursAlready = true; 528 } 529 TerrainPatch right = patch.rightNeighbour; 530 TerrainPatch down = patch.bottomNeighbour; 531 532 UpdatedTerrainPatch utp = updated.get(patch.getName()); 533 if (utp == null) { 534 utp = new UpdatedTerrainPatch(patch, patch.lod); 535 updated.put(utp.getName(), utp); 536 } 537 538 if (right != null) { 539 UpdatedTerrainPatch utpR = updated.get(right.getName()); 540 if (utpR == null) { 541 utpR = new UpdatedTerrainPatch(right, right.lod); 542 updated.put(utpR.getName(), utpR); 543 } 544 545 utp.setRightLod(utpR.getNewLod()); 546 utpR.setLeftLod(utp.getNewLod()); 547 } 548 if (down != null) { 549 UpdatedTerrainPatch utpD = updated.get(down.getName()); 550 if (utpD == null) { 551 utpD = new UpdatedTerrainPatch(down, down.lod); 552 updated.put(utpD.getName(), utpD); 553 } 554 555 utp.setBottomLod(utpD.getNewLod()); 556 utpD.setTopLod(utp.getNewLod()); 557 } 558 559 } 560 } 561 } 562 } 563 564 /** 565 * TerrainQuad caches neighbours for faster LOD checks. 566 * Sometimes you might want to reset this cache (for instance in TerrainGrid) 567 */ 568 protected void resetCachedNeighbours() { 569 if (children != null) { 570 for (int x = children.size(); --x >= 0;) { 571 Spatial child = children.get(x); 572 if (child instanceof TerrainQuad) { 573 ((TerrainQuad) child).resetCachedNeighbours(); 574 } else if (child instanceof TerrainPatch) { 575 TerrainPatch patch = (TerrainPatch) child; 576 patch.searchedForNeighboursAlready = false; 577 } 578 } 579 } 580 } 581 582 /** 583 * Find any neighbours that should have their edges seamed because another neighbour 584 * changed its LOD to a greater value (less detailed) 585 */ 586 protected synchronized void fixEdges(HashMap<String,UpdatedTerrainPatch> updated) { 587 if (children != null) { 588 for (int x = children.size(); --x >= 0;) { 589 Spatial child = children.get(x); 590 if (child instanceof TerrainQuad) { 591 ((TerrainQuad) child).fixEdges(updated); 592 } else if (child instanceof TerrainPatch) { 593 TerrainPatch patch = (TerrainPatch) child; 594 UpdatedTerrainPatch utp = updated.get(patch.getName()); 595 596 if(utp != null && utp.lodChanged()) { 597 if (!patch.searchedForNeighboursAlready) { 598 // set the references to the neighbours 599 patch.rightNeighbour = findRightPatch(patch); 600 patch.bottomNeighbour = findDownPatch(patch); 601 patch.leftNeighbour = findLeftPatch(patch); 602 patch.topNeighbour = findTopPatch(patch); 603 patch.searchedForNeighboursAlready = true; 604 } 605 TerrainPatch right = patch.rightNeighbour; 606 TerrainPatch down = patch.bottomNeighbour; 607 TerrainPatch top = patch.topNeighbour; 608 TerrainPatch left = patch.leftNeighbour; 609 if (right != null) { 610 UpdatedTerrainPatch utpR = updated.get(right.getName()); 611 if (utpR == null) { 612 utpR = new UpdatedTerrainPatch(right, right.lod); 613 updated.put(utpR.getName(), utpR); 614 } 615 utpR.setFixEdges(true); 616 } 617 if (down != null) { 618 UpdatedTerrainPatch utpD = updated.get(down.getName()); 619 if (utpD == null) { 620 utpD = new UpdatedTerrainPatch(down, down.lod); 621 updated.put(utpD.getName(), utpD); 622 } 623 utpD.setFixEdges(true); 624 } 625 if (top != null){ 626 UpdatedTerrainPatch utpT = updated.get(top.getName()); 627 if (utpT == null) { 628 utpT = new UpdatedTerrainPatch(top, top.lod); 629 updated.put(utpT.getName(), utpT); 630 } 631 utpT.setFixEdges(true); 632 } 633 if (left != null){ 634 UpdatedTerrainPatch utpL = updated.get(left.getName()); 635 if (utpL == null) { 636 utpL = new UpdatedTerrainPatch(left, left.lod); 637 updated.put(utpL.getName(), utpL); 638 } 639 utpL.setFixEdges(true); 640 } 641 } 642 } 643 } 644 } 645 } 646 647 protected synchronized void reIndexPages(HashMap<String,UpdatedTerrainPatch> updated, boolean usesVariableLod) { 648 if (children != null) { 649 for (int i = children.size(); --i >= 0;) { 650 Spatial child = children.get(i); 651 if (child instanceof TerrainQuad) { 652 ((TerrainQuad) child).reIndexPages(updated, usesVariableLod); 653 } else if (child instanceof TerrainPatch) { 654 ((TerrainPatch) child).reIndexGeometry(updated, usesVariableLod); 655 } 656 } 657 } 658 } 659 660 /** 661 * <code>split</code> divides the heightmap data for four children. The 662 * children are either quads or patches. This is dependent on the size of the 663 * children. If the child's size is less than or equal to the set block 664 * size, then patches are created, otherwise, quads are created. 665 * 666 * @param blockSize 667 * the blocks size to test against. 668 * @param heightMap 669 * the height data. 670 */ 671 protected void split(int blockSize, float[] heightMap) { 672 if ((size >> 1) + 1 <= blockSize) { 673 createQuadPatch(heightMap); 674 } else { 675 createQuad(blockSize, heightMap); 676 } 677 678 } 679 680 /** 681 * Quadrants, world coordinates, and heightmap coordinates (Y-up): 682 * 683 * -z 684 * -u | 685 * -v 1|3 686 * -x ----+---- x 687 * 2|4 u 688 * | v 689 * z 690 * <code>createQuad</code> generates four new quads from this quad. 691 * The heightmap's top left (0,0) coordinate is at the bottom, -x,-z 692 * coordinate of the terrain, so it grows in the positive x.z direction. 693 */ 694 protected void createQuad(int blockSize, float[] heightMap) { 695 // create 4 terrain quads 696 int quarterSize = size >> 2; 697 698 int split = (size + 1) >> 1; 699 700 Vector2f tempOffset = new Vector2f(); 701 offsetAmount += quarterSize; 702 703 //if (lodCalculator == null) 704 // lodCalculator = createDefaultLodCalculator(); // set a default one 705 706 // 1 upper left of heightmap, upper left quad 707 float[] heightBlock1 = createHeightSubBlock(heightMap, 0, 0, split); 708 709 Vector3f origin1 = new Vector3f(-quarterSize * stepScale.x, 0, 710 -quarterSize * stepScale.z); 711 712 tempOffset.x = offset.x; 713 tempOffset.y = offset.y; 714 tempOffset.x += origin1.x; 715 tempOffset.y += origin1.z; 716 717 TerrainQuad quad1 = new TerrainQuad(getName() + "Quad1", blockSize, 718 split, stepScale, heightBlock1, totalSize, tempOffset, 719 offsetAmount); 720 quad1.setLocalTranslation(origin1); 721 quad1.quadrant = 1; 722 this.attachChild(quad1); 723 724 // 2 lower left of heightmap, lower left quad 725 float[] heightBlock2 = createHeightSubBlock(heightMap, 0, split - 1, 726 split); 727 728 Vector3f origin2 = new Vector3f(-quarterSize * stepScale.x, 0, 729 quarterSize * stepScale.z); 730 731 tempOffset = new Vector2f(); 732 tempOffset.x = offset.x; 733 tempOffset.y = offset.y; 734 tempOffset.x += origin2.x; 735 tempOffset.y += origin2.z; 736 737 TerrainQuad quad2 = new TerrainQuad(getName() + "Quad2", blockSize, 738 split, stepScale, heightBlock2, totalSize, tempOffset, 739 offsetAmount); 740 quad2.setLocalTranslation(origin2); 741 quad2.quadrant = 2; 742 this.attachChild(quad2); 743 744 // 3 upper right of heightmap, upper right quad 745 float[] heightBlock3 = createHeightSubBlock(heightMap, split - 1, 0, 746 split); 747 748 Vector3f origin3 = new Vector3f(quarterSize * stepScale.x, 0, 749 -quarterSize * stepScale.z); 750 751 tempOffset = new Vector2f(); 752 tempOffset.x = offset.x; 753 tempOffset.y = offset.y; 754 tempOffset.x += origin3.x; 755 tempOffset.y += origin3.z; 756 757 TerrainQuad quad3 = new TerrainQuad(getName() + "Quad3", blockSize, 758 split, stepScale, heightBlock3, totalSize, tempOffset, 759 offsetAmount); 760 quad3.setLocalTranslation(origin3); 761 quad3.quadrant = 3; 762 this.attachChild(quad3); 763 764 // 4 lower right of heightmap, lower right quad 765 float[] heightBlock4 = createHeightSubBlock(heightMap, split - 1, 766 split - 1, split); 767 768 Vector3f origin4 = new Vector3f(quarterSize * stepScale.x, 0, 769 quarterSize * stepScale.z); 770 771 tempOffset = new Vector2f(); 772 tempOffset.x = offset.x; 773 tempOffset.y = offset.y; 774 tempOffset.x += origin4.x; 775 tempOffset.y += origin4.z; 776 777 TerrainQuad quad4 = new TerrainQuad(getName() + "Quad4", blockSize, 778 split, stepScale, heightBlock4, totalSize, tempOffset, 779 offsetAmount); 780 quad4.setLocalTranslation(origin4); 781 quad4.quadrant = 4; 782 this.attachChild(quad4); 783 784 } 785 786 public void generateDebugTangents(Material mat) { 787 for (int x = children.size(); --x >= 0;) { 788 Spatial child = children.get(x); 789 if (child instanceof TerrainQuad) { 790 ((TerrainQuad)child).generateDebugTangents(mat); 791 } else if (child instanceof TerrainPatch) { 792 Geometry debug = new Geometry( "Debug " + name, 793 TangentBinormalGenerator.genTbnLines( ((TerrainPatch)child).getMesh(), 0.8f)); 794 attachChild(debug); 795 debug.setLocalTranslation(child.getLocalTranslation()); 796 debug.setCullHint(CullHint.Never); 797 debug.setMaterial(mat); 798 } 799 } 800 } 801 802 /** 803 * <code>createQuadPatch</code> creates four child patches from this quad. 804 */ 805 protected void createQuadPatch(float[] heightMap) { 806 // create 4 terrain patches 807 int quarterSize = size >> 2; 808 int halfSize = size >> 1; 809 int split = (size + 1) >> 1; 810 811 //if (lodCalculator == null) 812 // lodCalculator = createDefaultLodCalculator(); // set a default one 813 814 offsetAmount += quarterSize; 815 816 // 1 lower left 817 float[] heightBlock1 = createHeightSubBlock(heightMap, 0, 0, split); 818 819 Vector3f origin1 = new Vector3f(-halfSize * stepScale.x, 0, -halfSize 820 * stepScale.z); 821 822 Vector2f tempOffset1 = new Vector2f(); 823 tempOffset1.x = offset.x; 824 tempOffset1.y = offset.y; 825 tempOffset1.x += origin1.x / 2; 826 tempOffset1.y += origin1.z / 2; 827 828 TerrainPatch patch1 = new TerrainPatch(getName() + "Patch1", split, 829 stepScale, heightBlock1, origin1, totalSize, tempOffset1, 830 offsetAmount); 831 patch1.setQuadrant((short) 1); 832 this.attachChild(patch1); 833 patch1.setModelBound(new BoundingBox()); 834 patch1.updateModelBound(); 835 //patch1.setLodCalculator(lodCalculator); 836 //TangentBinormalGenerator.generate(patch1); 837 838 // 2 upper left 839 float[] heightBlock2 = createHeightSubBlock(heightMap, 0, split - 1, 840 split); 841 842 Vector3f origin2 = new Vector3f(-halfSize * stepScale.x, 0, 0); 843 844 Vector2f tempOffset2 = new Vector2f(); 845 tempOffset2.x = offset.x; 846 tempOffset2.y = offset.y; 847 tempOffset2.x += origin1.x / 2; 848 tempOffset2.y += quarterSize * stepScale.z; 849 850 TerrainPatch patch2 = new TerrainPatch(getName() + "Patch2", split, 851 stepScale, heightBlock2, origin2, totalSize, tempOffset2, 852 offsetAmount); 853 patch2.setQuadrant((short) 2); 854 this.attachChild(patch2); 855 patch2.setModelBound(new BoundingBox()); 856 patch2.updateModelBound(); 857 //patch2.setLodCalculator(lodCalculator); 858 //TangentBinormalGenerator.generate(patch2); 859 860 // 3 lower right 861 float[] heightBlock3 = createHeightSubBlock(heightMap, split - 1, 0, 862 split); 863 864 Vector3f origin3 = new Vector3f(0, 0, -halfSize * stepScale.z); 865 866 Vector2f tempOffset3 = new Vector2f(); 867 tempOffset3.x = offset.x; 868 tempOffset3.y = offset.y; 869 tempOffset3.x += quarterSize * stepScale.x; 870 tempOffset3.y += origin3.z / 2; 871 872 TerrainPatch patch3 = new TerrainPatch(getName() + "Patch3", split, 873 stepScale, heightBlock3, origin3, totalSize, tempOffset3, 874 offsetAmount); 875 patch3.setQuadrant((short) 3); 876 this.attachChild(patch3); 877 patch3.setModelBound(new BoundingBox()); 878 patch3.updateModelBound(); 879 //patch3.setLodCalculator(lodCalculator); 880 //TangentBinormalGenerator.generate(patch3); 881 882 // 4 upper right 883 float[] heightBlock4 = createHeightSubBlock(heightMap, split - 1, 884 split - 1, split); 885 886 Vector3f origin4 = new Vector3f(0, 0, 0); 887 888 Vector2f tempOffset4 = new Vector2f(); 889 tempOffset4.x = offset.x; 890 tempOffset4.y = offset.y; 891 tempOffset4.x += quarterSize * stepScale.x; 892 tempOffset4.y += quarterSize * stepScale.z; 893 894 TerrainPatch patch4 = new TerrainPatch(getName() + "Patch4", split, 895 stepScale, heightBlock4, origin4, totalSize, tempOffset4, 896 offsetAmount); 897 patch4.setQuadrant((short) 4); 898 this.attachChild(patch4); 899 patch4.setModelBound(new BoundingBox()); 900 patch4.updateModelBound(); 901 //patch4.setLodCalculator(lodCalculator); 902 //TangentBinormalGenerator.generate(patch4); 903 } 904 905 public float[] createHeightSubBlock(float[] heightMap, int x, 906 int y, int side) { 907 float[] rVal = new float[side * side]; 908 int bsize = (int) FastMath.sqrt(heightMap.length); 909 int count = 0; 910 for (int i = y; i < side + y; i++) { 911 for (int j = x; j < side + x; j++) { 912 if (j < bsize && i < bsize) 913 rVal[count] = heightMap[j + (i * bsize)]; 914 count++; 915 } 916 } 917 return rVal; 918 } 919 920 /** 921 * A handy method that will attach all bounding boxes of this terrain 922 * to the node you supply. 923 * Useful to visualize the bounding boxes when debugging. 924 * 925 * @param parent that will get the bounding box shapes of the terrain attached to 926 */ 927 public void attachBoundChildren(Node parent) { 928 for (int i = 0; i < this.getQuantity(); i++) { 929 if (this.getChild(i) instanceof TerrainQuad) { 930 ((TerrainQuad) getChild(i)).attachBoundChildren(parent); 931 } else if (this.getChild(i) instanceof TerrainPatch) { 932 BoundingVolume bv = getChild(i).getWorldBound(); 933 if (bv instanceof BoundingBox) { 934 attachBoundingBox((BoundingBox)bv, parent); 935 } 936 } 937 } 938 BoundingVolume bv = getWorldBound(); 939 if (bv instanceof BoundingBox) { 940 attachBoundingBox((BoundingBox)bv, parent); 941 } 942 } 943 944 /** 945 * used by attachBoundChildren() 946 */ 947 private void attachBoundingBox(BoundingBox bb, Node parent) { 948 WireBox wb = new WireBox(bb.getXExtent(), bb.getYExtent(), bb.getZExtent()); 949 Geometry g = new Geometry(); 950 g.setMesh(wb); 951 g.setLocalTranslation(bb.getCenter()); 952 parent.attachChild(g); 953 } 954 955 /** 956 * Signal if the normal vectors for the terrain need to be recalculated. 957 * Does this by looking at the affectedAreaBBox bounding box. If the bbox 958 * exists already, then it will grow the box to fit the new changedPoint. 959 * If the affectedAreaBBox is null, then it will create one of unit size. 960 * 961 * @param needToRecalculateNormals if null, will cause needToRecalculateNormals() to return false 962 */ 963 protected void setNormalRecalcNeeded(Vector2f changedPoint) { 964 if (changedPoint == null) { // set needToRecalculateNormals() to false 965 affectedAreaBBox = null; 966 return; 967 } 968 969 if (affectedAreaBBox == null) { 970 affectedAreaBBox = new BoundingBox(new Vector3f(changedPoint.x, 0, changedPoint.y), 1f, Float.MAX_VALUE, 1f); // unit length 971 } else { 972 // adjust size of box to be larger 973 affectedAreaBBox.mergeLocal(new BoundingBox(new Vector3f(changedPoint.x, 0, changedPoint.y), 1f, Float.MAX_VALUE, 1f)); 974 } 975 } 976 977 protected boolean needToRecalculateNormals() { 978 if (affectedAreaBBox != null) 979 return true; 980 if (!lastScale.equals(getWorldScale())) { 981 affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), size, Float.MAX_VALUE, size); 982 lastScale = getWorldScale(); 983 return true; 984 } 985 return false; 986 } 987 988 /** 989 * This will cause all normals for this terrain quad to be recalculated 990 */ 991 protected void setNeedToRecalculateNormals() { 992 affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), size*2, Float.MAX_VALUE, size*2); 993 } 994 995 public float getHeightmapHeight(Vector2f xz) { 996 // offset 997 int halfSize = totalSize / 2; 998 int x = Math.round((xz.x / getWorldScale().x) + halfSize); 999 int z = Math.round((xz.y / getWorldScale().z) + halfSize); 1000 1001 return getHeightmapHeight(x, z); 1002 } 1003 1004 /** 1005 * This will just get the heightmap value at the supplied point, 1006 * not an interpolated (actual) height value. 1007 */ 1008 protected float getHeightmapHeight(int x, int z) { 1009 int quad = findQuadrant(x, z); 1010 int split = (size + 1) >> 1; 1011 if (children != null) { 1012 for (int i = children.size(); --i >= 0;) { 1013 Spatial spat = children.get(i); 1014 int col = x; 1015 int row = z; 1016 boolean match = false; 1017 1018 // get the childs quadrant 1019 int childQuadrant = 0; 1020 if (spat instanceof TerrainQuad) { 1021 childQuadrant = ((TerrainQuad) spat).getQuadrant(); 1022 } else if (spat instanceof TerrainPatch) { 1023 childQuadrant = ((TerrainPatch) spat).getQuadrant(); 1024 } 1025 1026 if (childQuadrant == 1 && (quad & 1) != 0) { 1027 match = true; 1028 } else if (childQuadrant == 2 && (quad & 2) != 0) { 1029 row = z - split + 1; 1030 match = true; 1031 } else if (childQuadrant == 3 && (quad & 4) != 0) { 1032 col = x - split + 1; 1033 match = true; 1034 } else if (childQuadrant == 4 && (quad & 8) != 0) { 1035 col = x - split + 1; 1036 row = z - split + 1; 1037 match = true; 1038 } 1039 1040 if (match) { 1041 if (spat instanceof TerrainQuad) { 1042 return ((TerrainQuad) spat).getHeightmapHeight(col, row); 1043 } else if (spat instanceof TerrainPatch) { 1044 return ((TerrainPatch) spat).getHeightmapHeight(col, row); 1045 } 1046 } 1047 1048 } 1049 } 1050 return Float.NaN; 1051 } 1052 1053 protected Vector3f getMeshNormal(int x, int z) { 1054 int quad = findQuadrant(x, z); 1055 int split = (size + 1) >> 1; 1056 if (children != null) { 1057 for (int i = children.size(); --i >= 0;) { 1058 Spatial spat = children.get(i); 1059 int col = x; 1060 int row = z; 1061 boolean match = false; 1062 1063 // get the childs quadrant 1064 int childQuadrant = 0; 1065 if (spat instanceof TerrainQuad) { 1066 childQuadrant = ((TerrainQuad) spat).getQuadrant(); 1067 } else if (spat instanceof TerrainPatch) { 1068 childQuadrant = ((TerrainPatch) spat).getQuadrant(); 1069 } 1070 1071 if (childQuadrant == 1 && (quad & 1) != 0) { 1072 match = true; 1073 } else if (childQuadrant == 2 && (quad & 2) != 0) { 1074 row = z - split + 1; 1075 match = true; 1076 } else if (childQuadrant == 3 && (quad & 4) != 0) { 1077 col = x - split + 1; 1078 match = true; 1079 } else if (childQuadrant == 4 && (quad & 8) != 0) { 1080 col = x - split + 1; 1081 row = z - split + 1; 1082 match = true; 1083 } 1084 1085 if (match) { 1086 if (spat instanceof TerrainQuad) { 1087 return ((TerrainQuad) spat).getMeshNormal(col, row); 1088 } else if (spat instanceof TerrainPatch) { 1089 return ((TerrainPatch) spat).getMeshNormal(col, row); 1090 } 1091 } 1092 1093 } 1094 } 1095 return null; 1096 } 1097 1098 public float getHeight(Vector2f xz) { 1099 // offset 1100 float x = (float)(((xz.x - getWorldTranslation().x) / getWorldScale().x) + (float)totalSize / 2f); 1101 float z = (float)(((xz.y - getWorldTranslation().z) / getWorldScale().z) + (float)totalSize / 2f); 1102 float height = getHeight(x, z); 1103 height *= getWorldScale().y; 1104 return height; 1105 } 1106 1107 /* 1108 * gets an interpolated value at the specified point 1109 * @param x coordinate translated into actual (positive) terrain grid coordinates 1110 * @param y coordinate translated into actual (positive) terrain grid coordinates 1111 */ 1112 protected float getHeight(float x, float z) { 1113 x-=0.5f; 1114 z-=0.5f; 1115 float col = FastMath.floor(x); 1116 float row = FastMath.floor(z); 1117 boolean onX = false; 1118 if(1 - (x - col)-(z - row) < 0) // what triangle to interpolate on 1119 onX = true; 1120 // v1--v2 ^ 1121 // | / | | 1122 // | / | | 1123 // v3--v4 | Z 1124 // | 1125 // <-------Y 1126 // X 1127 float v1 = getHeightmapHeight((int) FastMath.ceil(x), (int) FastMath.ceil(z)); 1128 float v2 = getHeightmapHeight((int) FastMath.floor(x), (int) FastMath.ceil(z)); 1129 float v3 = getHeightmapHeight((int) FastMath.ceil(x), (int) FastMath.floor(z)); 1130 float v4 = getHeightmapHeight((int) FastMath.floor(x), (int) FastMath.floor(z)); 1131 if (onX) { 1132 return ((x - col) + (z - row) - 1f)*v1 + (1f - (x - col))*v2 + (1f - (z - row))*v3; 1133 } else { 1134 return (1f - (x - col) - (z - row))*v4 + (z - row)*v2 + (x - col)*v3; 1135 } 1136 } 1137 1138 public Vector3f getNormal(Vector2f xz) { 1139 // offset 1140 float x = (float)(((xz.x - getWorldTranslation().x) / getWorldScale().x) + (float)totalSize / 2f); 1141 float z = (float)(((xz.y - getWorldTranslation().z) / getWorldScale().z) + (float)totalSize / 2f); 1142 Vector3f normal = getNormal(x, z, xz); 1143 1144 return normal; 1145 } 1146 1147 protected Vector3f getNormal(float x, float z, Vector2f xz) { 1148 x-=0.5f; 1149 z-=0.5f; 1150 float col = FastMath.floor(x); 1151 float row = FastMath.floor(z); 1152 boolean onX = false; 1153 if(1 - (x - col)-(z - row) < 0) // what triangle to interpolate on 1154 onX = true; 1155 // v1--v2 ^ 1156 // | / | | 1157 // | / | | 1158 // v3--v4 | Z 1159 // | 1160 // <-------Y 1161 // X 1162 Vector3f n1 = getMeshNormal((int) FastMath.ceil(x), (int) FastMath.ceil(z)); 1163 Vector3f n2 = getMeshNormal((int) FastMath.floor(x), (int) FastMath.ceil(z)); 1164 Vector3f n3 = getMeshNormal((int) FastMath.ceil(x), (int) FastMath.floor(z)); 1165 Vector3f n4 = getMeshNormal((int) FastMath.floor(x), (int) FastMath.floor(z)); 1166 1167 return n1.add(n2).add(n3).add(n4).normalize(); 1168 } 1169 1170 public void setHeight(Vector2f xz, float height) { 1171 List<Vector2f> coord = new ArrayList<Vector2f>(); 1172 coord.add(xz); 1173 List<Float> h = new ArrayList<Float>(); 1174 h.add(height); 1175 1176 setHeight(coord, h); 1177 } 1178 1179 public void adjustHeight(Vector2f xz, float delta) { 1180 List<Vector2f> coord = new ArrayList<Vector2f>(); 1181 coord.add(xz); 1182 List<Float> h = new ArrayList<Float>(); 1183 h.add(delta); 1184 1185 adjustHeight(coord, h); 1186 } 1187 1188 public void setHeight(List<Vector2f> xz, List<Float> height) { 1189 setHeight(xz, height, true); 1190 } 1191 1192 public void adjustHeight(List<Vector2f> xz, List<Float> height) { 1193 setHeight(xz, height, false); 1194 } 1195 1196 protected void setHeight(List<Vector2f> xz, List<Float> height, boolean overrideHeight) { 1197 if (xz.size() != height.size()) 1198 throw new IllegalArgumentException("Both lists must be the same length!"); 1199 1200 int halfSize = totalSize / 2; 1201 1202 List<LocationHeight> locations = new ArrayList<LocationHeight>(); 1203 1204 // offset 1205 for (int i=0; i<xz.size(); i++) { 1206 int x = Math.round((xz.get(i).x / getWorldScale().x) + halfSize); 1207 int z = Math.round((xz.get(i).y / getWorldScale().z) + halfSize); 1208 locations.add(new LocationHeight(x,z,height.get(i))); 1209 } 1210 1211 setHeight(locations, overrideHeight); // adjust height of the actual mesh 1212 1213 // signal that the normals need updating 1214 for (int i=0; i<xz.size(); i++) 1215 setNormalRecalcNeeded(xz.get(i) ); 1216 } 1217 1218 protected class LocationHeight { 1219 int x; 1220 int z; 1221 float h; 1222 1223 LocationHeight(){} 1224 1225 LocationHeight(int x, int z, float h){ 1226 this.x = x; 1227 this.z = z; 1228 this.h = h; 1229 } 1230 } 1231 1232 protected void setHeight(List<LocationHeight> locations, boolean overrideHeight) { 1233 if (children == null) 1234 return; 1235 1236 List<LocationHeight> quadLH1 = new ArrayList<LocationHeight>(); 1237 List<LocationHeight> quadLH2 = new ArrayList<LocationHeight>(); 1238 List<LocationHeight> quadLH3 = new ArrayList<LocationHeight>(); 1239 List<LocationHeight> quadLH4 = new ArrayList<LocationHeight>(); 1240 Spatial quad1 = null; 1241 Spatial quad2 = null; 1242 Spatial quad3 = null; 1243 Spatial quad4 = null; 1244 1245 // get the child quadrants 1246 for (int i = children.size(); --i >= 0;) { 1247 Spatial spat = children.get(i); 1248 int childQuadrant = 0; 1249 if (spat instanceof TerrainQuad) { 1250 childQuadrant = ((TerrainQuad) spat).getQuadrant(); 1251 } else if (spat instanceof TerrainPatch) { 1252 childQuadrant = ((TerrainPatch) spat).getQuadrant(); 1253 } 1254 1255 if (childQuadrant == 1) 1256 quad1 = spat; 1257 else if (childQuadrant == 2) 1258 quad2 = spat; 1259 else if (childQuadrant == 3) 1260 quad3 = spat; 1261 else if (childQuadrant == 4) 1262 quad4 = spat; 1263 } 1264 1265 int split = (size + 1) >> 1; 1266 1267 // distribute each locationHeight into the quadrant it intersects 1268 for (LocationHeight lh : locations) { 1269 int quad = findQuadrant(lh.x, lh.z); 1270 1271 int col = lh.x; 1272 int row = lh.z; 1273 1274 if ((quad & 1) != 0) { 1275 quadLH1.add(lh); 1276 } 1277 if ((quad & 2) != 0) { 1278 row = lh.z - split + 1; 1279 quadLH2.add(new LocationHeight(lh.x, row, lh.h)); 1280 } 1281 if ((quad & 4) != 0) { 1282 col = lh.x - split + 1; 1283 quadLH3.add(new LocationHeight(col, lh.z, lh.h)); 1284 } 1285 if ((quad & 8) != 0) { 1286 col = lh.x - split + 1; 1287 row = lh.z - split + 1; 1288 quadLH4.add(new LocationHeight(col, row, lh.h)); 1289 } 1290 } 1291 1292 // send the locations to the children 1293 if (!quadLH1.isEmpty()) { 1294 if (quad1 instanceof TerrainQuad) 1295 ((TerrainQuad)quad1).setHeight(quadLH1, overrideHeight); 1296 else if(quad1 instanceof TerrainPatch) 1297 ((TerrainPatch)quad1).setHeight(quadLH1, overrideHeight); 1298 } 1299 1300 if (!quadLH2.isEmpty()) { 1301 if (quad2 instanceof TerrainQuad) 1302 ((TerrainQuad)quad2).setHeight(quadLH2, overrideHeight); 1303 else if(quad2 instanceof TerrainPatch) 1304 ((TerrainPatch)quad2).setHeight(quadLH2, overrideHeight); 1305 } 1306 1307 if (!quadLH3.isEmpty()) { 1308 if (quad3 instanceof TerrainQuad) 1309 ((TerrainQuad)quad3).setHeight(quadLH3, overrideHeight); 1310 else if(quad3 instanceof TerrainPatch) 1311 ((TerrainPatch)quad3).setHeight(quadLH3, overrideHeight); 1312 } 1313 1314 if (!quadLH4.isEmpty()) { 1315 if (quad4 instanceof TerrainQuad) 1316 ((TerrainQuad)quad4).setHeight(quadLH4, overrideHeight); 1317 else if(quad4 instanceof TerrainPatch) 1318 ((TerrainPatch)quad4).setHeight(quadLH4, overrideHeight); 1319 } 1320 } 1321 1322 protected boolean isPointOnTerrain(int x, int z) { 1323 return (x >= 0 && x <= totalSize && z >= 0 && z <= totalSize); 1324 } 1325 1326 1327 public int getTerrainSize() { 1328 return totalSize; 1329 } 1330 1331 1332 // a position can be in multiple quadrants, so use a bit anded value. 1333 private int findQuadrant(int x, int y) { 1334 int split = (size + 1) >> 1; 1335 int quads = 0; 1336 if (x < split && y < split) 1337 quads |= 1; 1338 if (x < split && y >= split - 1) 1339 quads |= 2; 1340 if (x >= split - 1 && y < split) 1341 quads |= 4; 1342 if (x >= split - 1 && y >= split - 1) 1343 quads |= 8; 1344 return quads; 1345 } 1346 1347 /** 1348 * lock or unlock the meshes of this terrain. 1349 * Locked meshes are uneditable but have better performance. 1350 * @param locked or unlocked 1351 */ 1352 public void setLocked(boolean locked) { 1353 for (int i = 0; i < this.getQuantity(); i++) { 1354 if (this.getChild(i) instanceof TerrainQuad) { 1355 ((TerrainQuad) getChild(i)).setLocked(locked); 1356 } else if (this.getChild(i) instanceof TerrainPatch) { 1357 if (locked) 1358 ((TerrainPatch) getChild(i)).lockMesh(); 1359 else 1360 ((TerrainPatch) getChild(i)).unlockMesh(); 1361 } 1362 } 1363 } 1364 1365 1366 public int getQuadrant() { 1367 return quadrant; 1368 } 1369 1370 public void setQuadrant(short quadrant) { 1371 this.quadrant = quadrant; 1372 } 1373 1374 1375 protected TerrainPatch getPatch(int quad) { 1376 if (children != null) 1377 for (int x = children.size(); --x >= 0;) { 1378 Spatial child = children.get(x); 1379 if (child instanceof TerrainPatch) { 1380 TerrainPatch tb = (TerrainPatch) child; 1381 if (tb.getQuadrant() == quad) 1382 return tb; 1383 } 1384 } 1385 return null; 1386 } 1387 1388 protected TerrainQuad getQuad(int quad) { 1389 if (children != null) 1390 for (int x = children.size(); --x >= 0;) { 1391 Spatial child = children.get(x); 1392 if (child instanceof TerrainQuad) { 1393 TerrainQuad tq = (TerrainQuad) child; 1394 if (tq.getQuadrant() == quad) 1395 return tq; 1396 } 1397 } 1398 return null; 1399 } 1400 1401 protected TerrainPatch findRightPatch(TerrainPatch tp) { 1402 if (tp.getQuadrant() == 1) 1403 return getPatch(3); 1404 else if (tp.getQuadrant() == 2) 1405 return getPatch(4); 1406 else if (tp.getQuadrant() == 3) { 1407 // find the patch to the right and ask it for child 1. 1408 TerrainQuad quad = findRightQuad(); 1409 if (quad != null) 1410 return quad.getPatch(1); 1411 } else if (tp.getQuadrant() == 4) { 1412 // find the patch to the right and ask it for child 2. 1413 TerrainQuad quad = findRightQuad(); 1414 if (quad != null) 1415 return quad.getPatch(2); 1416 } 1417 1418 return null; 1419 } 1420 1421 protected TerrainPatch findDownPatch(TerrainPatch tp) { 1422 if (tp.getQuadrant() == 1) 1423 return getPatch(2); 1424 else if (tp.getQuadrant() == 3) 1425 return getPatch(4); 1426 else if (tp.getQuadrant() == 2) { 1427 // find the patch below and ask it for child 1. 1428 TerrainQuad quad = findDownQuad(); 1429 if (quad != null) 1430 return quad.getPatch(1); 1431 } else if (tp.getQuadrant() == 4) { 1432 TerrainQuad quad = findDownQuad(); 1433 if (quad != null) 1434 return quad.getPatch(3); 1435 } 1436 1437 return null; 1438 } 1439 1440 1441 protected TerrainPatch findTopPatch(TerrainPatch tp) { 1442 if (tp.getQuadrant() == 2) 1443 return getPatch(1); 1444 else if (tp.getQuadrant() == 4) 1445 return getPatch(3); 1446 else if (tp.getQuadrant() == 1) { 1447 // find the patch above and ask it for child 2. 1448 TerrainQuad quad = findTopQuad(); 1449 if (quad != null) 1450 return quad.getPatch(2); 1451 } else if (tp.getQuadrant() == 3) { 1452 TerrainQuad quad = findTopQuad(); 1453 if (quad != null) 1454 return quad.getPatch(4); 1455 } 1456 1457 return null; 1458 } 1459 1460 protected TerrainPatch findLeftPatch(TerrainPatch tp) { 1461 if (tp.getQuadrant() == 3) 1462 return getPatch(1); 1463 else if (tp.getQuadrant() == 4) 1464 return getPatch(2); 1465 else if (tp.getQuadrant() == 1) { 1466 // find the patch above and ask it for child 2. 1467 TerrainQuad quad = findLeftQuad(); 1468 if (quad != null) 1469 return quad.getPatch(3); 1470 } else if (tp.getQuadrant() == 2) { 1471 TerrainQuad quad = findLeftQuad(); 1472 if (quad != null) 1473 return quad.getPatch(4); 1474 } 1475 1476 return null; 1477 } 1478 1479 protected TerrainQuad findRightQuad() { 1480 if (getParent() == null || !(getParent() instanceof TerrainQuad)) 1481 return null; 1482 1483 TerrainQuad pQuad = (TerrainQuad) getParent(); 1484 1485 if (quadrant == 1) 1486 return pQuad.getQuad(3); 1487 else if (quadrant == 2) 1488 return pQuad.getQuad(4); 1489 else if (quadrant == 3) { 1490 TerrainQuad quad = pQuad.findRightQuad(); 1491 if (quad != null) 1492 return quad.getQuad(1); 1493 } else if (quadrant == 4) { 1494 TerrainQuad quad = pQuad.findRightQuad(); 1495 if (quad != null) 1496 return quad.getQuad(2); 1497 } 1498 1499 return null; 1500 } 1501 1502 protected TerrainQuad findDownQuad() { 1503 if (getParent() == null || !(getParent() instanceof TerrainQuad)) 1504 return null; 1505 1506 TerrainQuad pQuad = (TerrainQuad) getParent(); 1507 1508 if (quadrant == 1) 1509 return pQuad.getQuad(2); 1510 else if (quadrant == 3) 1511 return pQuad.getQuad(4); 1512 else if (quadrant == 2) { 1513 TerrainQuad quad = pQuad.findDownQuad(); 1514 if (quad != null) 1515 return quad.getQuad(1); 1516 } else if (quadrant == 4) { 1517 TerrainQuad quad = pQuad.findDownQuad(); 1518 if (quad != null) 1519 return quad.getQuad(3); 1520 } 1521 1522 return null; 1523 } 1524 1525 protected TerrainQuad findTopQuad() { 1526 if (getParent() == null || !(getParent() instanceof TerrainQuad)) 1527 return null; 1528 1529 TerrainQuad pQuad = (TerrainQuad) getParent(); 1530 1531 if (quadrant == 2) 1532 return pQuad.getQuad(1); 1533 else if (quadrant == 4) 1534 return pQuad.getQuad(3); 1535 else if (quadrant == 1) { 1536 TerrainQuad quad = pQuad.findTopQuad(); 1537 if (quad != null) 1538 return quad.getQuad(2); 1539 } else if (quadrant == 3) { 1540 TerrainQuad quad = pQuad.findTopQuad(); 1541 if (quad != null) 1542 return quad.getQuad(4); 1543 } 1544 1545 return null; 1546 } 1547 1548 protected TerrainQuad findLeftQuad() { 1549 if (getParent() == null || !(getParent() instanceof TerrainQuad)) 1550 return null; 1551 1552 TerrainQuad pQuad = (TerrainQuad) getParent(); 1553 1554 if (quadrant == 3) 1555 return pQuad.getQuad(1); 1556 else if (quadrant == 4) 1557 return pQuad.getQuad(2); 1558 else if (quadrant == 1) { 1559 TerrainQuad quad = pQuad.findLeftQuad(); 1560 if (quad != null) 1561 return quad.getQuad(3); 1562 } else if (quadrant == 2) { 1563 TerrainQuad quad = pQuad.findLeftQuad(); 1564 if (quad != null) 1565 return quad.getQuad(4); 1566 } 1567 1568 return null; 1569 } 1570 1571 /** 1572 * Find what terrain patches need normal recalculations and update 1573 * their normals; 1574 */ 1575 protected void fixNormals(BoundingBox affectedArea) { 1576 if (children == null) 1577 return; 1578 1579 // go through the children and see if they collide with the affectedAreaBBox 1580 // if they do, then update their normals 1581 for (int x = children.size(); --x >= 0;) { 1582 Spatial child = children.get(x); 1583 if (child instanceof TerrainQuad) { 1584 if (affectedArea != null && affectedArea.intersects(((TerrainQuad) child).getWorldBound()) ) 1585 ((TerrainQuad) child).fixNormals(affectedArea); 1586 } else if (child instanceof TerrainPatch) { 1587 if (affectedArea != null && affectedArea.intersects(((TerrainPatch) child).getWorldBound()) ) 1588 ((TerrainPatch) child).updateNormals(); // recalculate the patch's normals 1589 } 1590 } 1591 } 1592 1593 /** 1594 * fix the normals on the edge of the terrain patches. 1595 */ 1596 protected void fixNormalEdges(BoundingBox affectedArea) { 1597 if (children == null) 1598 return; 1599 1600 for (int x = children.size(); --x >= 0;) { 1601 Spatial child = children.get(x); 1602 if (child instanceof TerrainQuad) { 1603 if (affectedArea != null && affectedArea.intersects(((TerrainQuad) child).getWorldBound()) ) 1604 ((TerrainQuad) child).fixNormalEdges(affectedArea); 1605 } else if (child instanceof TerrainPatch) { 1606 if (affectedArea != null && !affectedArea.intersects(((TerrainPatch) child).getWorldBound()) ) // if doesn't intersect, continue 1607 continue; 1608 1609 TerrainPatch tp = (TerrainPatch) child; 1610 TerrainPatch right = findRightPatch(tp); 1611 TerrainPatch bottom = findDownPatch(tp); 1612 TerrainPatch top = findTopPatch(tp); 1613 TerrainPatch left = findLeftPatch(tp); 1614 TerrainPatch topLeft = null; 1615 if (top != null) 1616 topLeft = findLeftPatch(top); 1617 TerrainPatch bottomRight = null; 1618 if (right != null) 1619 bottomRight = findDownPatch(right); 1620 TerrainPatch topRight = null; 1621 if (top != null) 1622 topRight = findRightPatch(top); 1623 TerrainPatch bottomLeft = null; 1624 if (left != null) 1625 bottomLeft = findDownPatch(left); 1626 1627 tp.fixNormalEdges(right, bottom, top, left, bottomRight, bottomLeft, topRight, topLeft); 1628 1629 } 1630 } // for each child 1631 1632 } 1633 1634 1635 1636 @Override 1637 public int collideWith(Collidable other, CollisionResults results){ 1638 int total = 0; 1639 1640 if (other instanceof Ray) 1641 return collideWithRay((Ray)other, results); 1642 1643 // if it didn't collide with this bbox, return 1644 if (other instanceof BoundingVolume) 1645 if (!this.getWorldBound().intersects((BoundingVolume)other)) 1646 return total; 1647 1648 for (Spatial child : children){ 1649 total += child.collideWith(other, results); 1650 } 1651 return total; 1652 } 1653 1654 /** 1655 * Gather the terrain patches that intersect the given ray (toTest). 1656 * This only tests the bounding boxes 1657 * @param toTest 1658 * @param results 1659 */ 1660 public void findPick(Ray toTest, List<TerrainPickData> results) { 1661 1662 if (getWorldBound() != null) { 1663 if (getWorldBound().intersects(toTest)) { 1664 // further checking needed. 1665 for (int i = 0; i < getQuantity(); i++) { 1666 if (children.get(i) instanceof TerrainPatch) { 1667 TerrainPatch tp = (TerrainPatch) children.get(i); 1668 tp.ensurePositiveVolumeBBox(); 1669 if (tp.getWorldBound().intersects(toTest)) { 1670 CollisionResults cr = new CollisionResults(); 1671 toTest.collideWith(tp.getWorldBound(), cr); 1672 if (cr != null && cr.getClosestCollision() != null) { 1673 cr.getClosestCollision().getDistance(); 1674 results.add(new TerrainPickData(tp, cr.getClosestCollision())); 1675 } 1676 } 1677 } 1678 else if (children.get(i) instanceof TerrainQuad) { 1679 ((TerrainQuad) children.get(i)).findPick(toTest, results); 1680 } 1681 } 1682 } 1683 } 1684 } 1685 1686 1687 /** 1688 * Retrieve all Terrain Patches from all children and store them 1689 * in the 'holder' list 1690 * @param holder must not be null, will be populated when returns 1691 */ 1692 public void getAllTerrainPatches(List<TerrainPatch> holder) { 1693 if (children != null) { 1694 for (int i = children.size(); --i >= 0;) { 1695 Spatial child = children.get(i); 1696 if (child instanceof TerrainQuad) { 1697 ((TerrainQuad) child).getAllTerrainPatches(holder); 1698 } else if (child instanceof TerrainPatch) { 1699 holder.add((TerrainPatch)child); 1700 } 1701 } 1702 } 1703 } 1704 1705 public void getAllTerrainPatchesWithTranslation(Map<TerrainPatch,Vector3f> holder, Vector3f translation) { 1706 if (children != null) { 1707 for (int i = children.size(); --i >= 0;) { 1708 Spatial child = children.get(i); 1709 if (child instanceof TerrainQuad) { 1710 ((TerrainQuad) child).getAllTerrainPatchesWithTranslation(holder, translation.clone().add(child.getLocalTranslation())); 1711 } else if (child instanceof TerrainPatch) { 1712 //if (holder.size() < 4) 1713 holder.put((TerrainPatch)child, translation.clone().add(child.getLocalTranslation())); 1714 } 1715 } 1716 } 1717 } 1718 1719 @Override 1720 public void read(JmeImporter e) throws IOException { 1721 super.read(e); 1722 InputCapsule c = e.getCapsule(this); 1723 size = c.readInt("size", 0); 1724 stepScale = (Vector3f) c.readSavable("stepScale", null); 1725 offset = (Vector2f) c.readSavable("offset", new Vector2f(0,0)); 1726 offsetAmount = c.readFloat("offsetAmount", 0); 1727 quadrant = c.readInt("quadrant", 0); 1728 totalSize = c.readInt("totalSize", 0); 1729 //lodCalculator = (LodCalculator) c.readSavable("lodCalculator", createDefaultLodCalculator()); 1730 //lodCalculatorFactory = (LodCalculatorFactory) c.readSavable("lodCalculatorFactory", null); 1731 1732 if ( !(getParent() instanceof TerrainQuad) ) { 1733 BoundingBox all = new BoundingBox(getWorldTranslation(), totalSize, totalSize, totalSize); 1734 affectedAreaBBox = all; 1735 updateNormals(); 1736 } 1737 } 1738 1739 @Override 1740 public void write(JmeExporter e) throws IOException { 1741 super.write(e); 1742 OutputCapsule c = e.getCapsule(this); 1743 c.write(size, "size", 0); 1744 c.write(totalSize, "totalSize", 0); 1745 c.write(stepScale, "stepScale", null); 1746 c.write(offset, "offset", new Vector2f(0,0)); 1747 c.write(offsetAmount, "offsetAmount", 0); 1748 c.write(quadrant, "quadrant", 0); 1749 //c.write(lodCalculatorFactory, "lodCalculatorFactory", null); 1750 //c.write(lodCalculator, "lodCalculator", null); 1751 } 1752 1753 @Override 1754 public TerrainQuad clone() { 1755 return this.clone(true); 1756 } 1757 1758 @Override 1759 public TerrainQuad clone(boolean cloneMaterials) { 1760 TerrainQuad quadClone = (TerrainQuad) super.clone(cloneMaterials); 1761 quadClone.name = name.toString(); 1762 quadClone.size = size; 1763 quadClone.totalSize = totalSize; 1764 if (stepScale != null) { 1765 quadClone.stepScale = stepScale.clone(); 1766 } 1767 if (offset != null) { 1768 quadClone.offset = offset.clone(); 1769 } 1770 quadClone.offsetAmount = offsetAmount; 1771 quadClone.quadrant = quadrant; 1772 //quadClone.lodCalculatorFactory = lodCalculatorFactory.clone(); 1773 //quadClone.lodCalculator = lodCalculator.clone(); 1774 1775 TerrainLodControl lodControlCloned = this.getControl(TerrainLodControl.class); 1776 TerrainLodControl lodControl = quadClone.getControl(TerrainLodControl.class); 1777 1778 if (lodControlCloned != null && !(getParent() instanceof TerrainQuad)) { 1779 //lodControlCloned.setLodCalculator(lodControl.getLodCalculator().clone()); 1780 } 1781 NormalRecalcControl normalControl = getControl(NormalRecalcControl.class); 1782 if (normalControl != null) 1783 normalControl.setTerrain(this); 1784 1785 return quadClone; 1786 } 1787 1788 public int getMaxLod() { 1789 if (maxLod < 0) 1790 maxLod = Math.max(1, (int) (FastMath.log(size-1)/FastMath.log(2)) -1); // -1 forces our minimum of 4 triangles wide 1791 1792 return maxLod; 1793 } 1794 1795 public int getPatchSize() { 1796 return patchSize; 1797 } 1798 1799 public int getTotalSize() { 1800 return totalSize; 1801 } 1802 1803 public float[] getHeightMap() { 1804 1805 float[] hm = null; 1806 int length = ((size-1)/2)+1; 1807 int area = size*size; 1808 hm = new float[area]; 1809 1810 if (getChildren() != null && !getChildren().isEmpty()) { 1811 float[] ul=null, ur=null, bl=null, br=null; 1812 // get the child heightmaps 1813 if (getChild(0) instanceof TerrainPatch) { 1814 for (Spatial s : getChildren()) { 1815 if ( ((TerrainPatch)s).getQuadrant() == 1) 1816 ul = ((TerrainPatch)s).getHeightMap(); 1817 else if(((TerrainPatch) s).getQuadrant() == 2) 1818 bl = ((TerrainPatch)s).getHeightMap(); 1819 else if(((TerrainPatch) s).getQuadrant() == 3) 1820 ur = ((TerrainPatch)s).getHeightMap(); 1821 else if(((TerrainPatch) s).getQuadrant() == 4) 1822 br = ((TerrainPatch)s).getHeightMap(); 1823 } 1824 } 1825 else { 1826 ul = getQuad(1).getHeightMap(); 1827 bl = getQuad(2).getHeightMap(); 1828 ur = getQuad(3).getHeightMap(); 1829 br = getQuad(4).getHeightMap(); 1830 } 1831 1832 // combine them into a single heightmap 1833 1834 1835 // first upper blocks 1836 for (int y=0; y<length; y++) { // rows 1837 for (int x1=0; x1<length; x1++) { 1838 int row = y*size; 1839 hm[row+x1] = ul[y*length+x1]; 1840 } 1841 for (int x2=1; x2<length; x2++) { 1842 int row = y*size + length; 1843 hm[row+x2-1] = ur[y*length + x2]; 1844 } 1845 } 1846 // second lower blocks 1847 int rowOffset = size*length; 1848 for (int y=1; y<length; y++) { // rows 1849 for (int x1=0; x1<length; x1++) { 1850 int row = (y-1)*size; 1851 hm[rowOffset+row+x1] = bl[y*length+x1]; 1852 } 1853 for (int x2=1; x2<length; x2++) { 1854 int row = (y-1)*size + length; 1855 hm[rowOffset+row+x2-1] = br[y*length + x2]; 1856 } 1857 } 1858 } 1859 1860 return hm; 1861 } 1862} 1863 1864