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.BoundingSphere; 37import com.jme3.bounding.BoundingVolume; 38import com.jme3.collision.Collidable; 39import com.jme3.collision.CollisionResults; 40import com.jme3.collision.UnsupportedCollisionException; 41import com.jme3.export.InputCapsule; 42import com.jme3.export.JmeExporter; 43import com.jme3.export.JmeImporter; 44import com.jme3.export.OutputCapsule; 45import com.jme3.math.*; 46import com.jme3.scene.Geometry; 47import com.jme3.scene.Mesh; 48import com.jme3.scene.VertexBuffer; 49import com.jme3.scene.VertexBuffer.Type; 50import com.jme3.terrain.geomipmap.TerrainQuad.LocationHeight; 51import com.jme3.terrain.geomipmap.lodcalc.util.EntropyComputeUtil; 52import com.jme3.util.BufferUtils; 53import java.io.IOException; 54import java.nio.FloatBuffer; 55import java.nio.IntBuffer; 56import java.util.HashMap; 57import java.util.List; 58 59 60/** 61 * A terrain patch is a leaf in the terrain quad tree. It has a mesh that can change levels of detail (LOD) 62 * whenever the view point, or camera, changes. The actual terrain mesh is created by the LODGeomap class. 63 * That uses a geo-mipmapping algorithm to change the index buffer of the mesh. 64 * The mesh is a triangle strip. In wireframe mode you might notice some strange lines, these are degenerate 65 * triangles generated by the geoMipMap algorithm and can be ignored. The video card removes them at almost no cost. 66 * 67 * Each patch needs to know its neighbour's LOD so it can seam its edges with them, in case the neighbour has a different 68 * LOD. If this doesn't happen, you will see gaps. 69 * 70 * The LOD value is most detailed at zero. It gets less detailed the higher the LOD value until you reach maxLod, which 71 * is a mathematical limit on the number of times the 'size' of the patch can be divided by two. However there is a -1 to that 72 * for now until I add in a custom index buffer calculation for that max level, the current algorithm does not go that far. 73 * 74 * You can supply a LodThresholdCalculator for use in determining when the LOD should change. It's API will no doubt change 75 * in the near future. Right now it defaults to just changing LOD every two patch sizes. So if a patch has a size of 65, 76 * then the LOD changes every 130 units away. 77 * 78 * @author Brent Owens 79 */ 80public class TerrainPatch extends Geometry { 81 82 protected LODGeomap geomap; 83 protected int lod = -1; // this terrain patch's LOD 84 private int maxLod = -1; 85 protected int previousLod = -1; 86 protected int lodLeft, lodTop, lodRight, lodBottom; // it's neighbour's LODs 87 88 protected int size; 89 90 protected int totalSize; 91 92 protected short quadrant = 1; 93 94 // x/z step 95 protected Vector3f stepScale; 96 97 // center of the patch in relation to (0,0,0) 98 protected Vector2f offset; 99 100 // amount the patch has been shifted. 101 protected float offsetAmount; 102 103 //protected LodCalculator lodCalculator; 104 //protected LodCalculatorFactory lodCalculatorFactory; 105 106 protected TerrainPatch leftNeighbour, topNeighbour, rightNeighbour, bottomNeighbour; 107 protected boolean searchedForNeighboursAlready = false; 108 109 110 protected float[] lodEntropy; 111 112 public TerrainPatch() { 113 super("TerrainPatch"); 114 } 115 116 public TerrainPatch(String name) { 117 super(name); 118 } 119 120 public TerrainPatch(String name, int size) { 121 this(name, size, new Vector3f(1,1,1), null, new Vector3f(0,0,0)); 122 } 123 124 /** 125 * Constructor instantiates a new <code>TerrainPatch</code> object. The 126 * parameters and heightmap data are then processed to generate a 127 * <code>TriMesh</code> object for rendering. 128 * 129 * @param name 130 * the name of the terrain patch. 131 * @param size 132 * the size of the heightmap. 133 * @param stepScale 134 * the scale for the axes. 135 * @param heightMap 136 * the height data. 137 * @param origin 138 * the origin offset of the patch. 139 */ 140 public TerrainPatch(String name, int size, Vector3f stepScale, 141 float[] heightMap, Vector3f origin) { 142 this(name, size, stepScale, heightMap, origin, size, new Vector2f(), 0); 143 } 144 145 /** 146 * Constructor instantiates a new <code>TerrainPatch</code> object. The 147 * parameters and heightmap data are then processed to generate a 148 * <code>TriMesh</code> object for renderering. 149 * 150 * @param name 151 * the name of the terrain patch. 152 * @param size 153 * the size of the patch. 154 * @param stepScale 155 * the scale for the axes. 156 * @param heightMap 157 * the height data. 158 * @param origin 159 * the origin offset of the patch. 160 * @param totalSize 161 * the total size of the terrain. (Higher if the patch is part of 162 * a <code>TerrainQuad</code> tree. 163 * @param offset 164 * the offset for texture coordinates. 165 * @param offsetAmount 166 * the total offset amount. Used for texture coordinates. 167 */ 168 public TerrainPatch(String name, int size, Vector3f stepScale, 169 float[] heightMap, Vector3f origin, int totalSize, 170 Vector2f offset, float offsetAmount) { 171 super(name); 172 this.size = size; 173 this.stepScale = stepScale; 174 this.totalSize = totalSize; 175 this.offsetAmount = offsetAmount; 176 this.offset = offset; 177 178 setLocalTranslation(origin); 179 180 geomap = new LODGeomap(size, heightMap); 181 Mesh m = geomap.createMesh(stepScale, new Vector2f(1,1), offset, offsetAmount, totalSize, false); 182 setMesh(m); 183 184 } 185 186 /** 187 * This calculation is slow, so don't use it often. 188 */ 189 public void generateLodEntropies() { 190 float[] entropies = new float[getMaxLod()+1]; 191 for (int i = 0; i <= getMaxLod(); i++){ 192 int curLod = (int) Math.pow(2, i); 193 IntBuffer buf = geomap.writeIndexArrayLodDiff(null, curLod, false, false, false, false); 194 entropies[i] = EntropyComputeUtil.computeLodEntropy(mesh, buf); 195 } 196 197 lodEntropy = entropies; 198 } 199 200 public float[] getLodEntropies(){ 201 if (lodEntropy == null){ 202 generateLodEntropies(); 203 } 204 return lodEntropy; 205 } 206 207 @Deprecated 208 public FloatBuffer getHeightmap() { 209 return BufferUtils.createFloatBuffer(geomap.getHeightArray()); 210 } 211 212 public float[] getHeightMap() { 213 return geomap.getHeightArray(); 214 } 215 216 /** 217 * The maximum lod supported by this terrain patch. 218 * If the patch size is 32 then the returned value would be log2(32)-2 = 3 219 * You can then use that value, 3, to see how many times you can divide 32 by 2 220 * before the terrain gets too un-detailed (can't stitch it any further). 221 * @return 222 */ 223 public int getMaxLod() { 224 if (maxLod < 0) 225 maxLod = Math.max(1, (int) (FastMath.log(size-1)/FastMath.log(2)) -1); // -1 forces our minimum of 4 triangles wide 226 227 return maxLod; 228 } 229 230 protected void reIndexGeometry(HashMap<String,UpdatedTerrainPatch> updated, boolean useVariableLod) { 231 232 UpdatedTerrainPatch utp = updated.get(getName()); 233 234 if (utp != null && (utp.isReIndexNeeded() || utp.isFixEdges()) ) { 235 int pow = (int) Math.pow(2, utp.getNewLod()); 236 boolean left = utp.getLeftLod() > utp.getNewLod(); 237 boolean top = utp.getTopLod() > utp.getNewLod(); 238 boolean right = utp.getRightLod() > utp.getNewLod(); 239 boolean bottom = utp.getBottomLod() > utp.getNewLod(); 240 241 IntBuffer ib = null; 242 if (useVariableLod) 243 ib = geomap.writeIndexArrayLodVariable(null, pow, (int) Math.pow(2, utp.getRightLod()), (int) Math.pow(2, utp.getTopLod()), (int) Math.pow(2, utp.getLeftLod()), (int) Math.pow(2, utp.getBottomLod())); 244 else 245 ib = geomap.writeIndexArrayLodDiff(null, pow, right, top, left, bottom); 246 utp.setNewIndexBuffer(ib); 247 } 248 249 } 250 251 252 public Vector2f getTex(float x, float z, Vector2f store) { 253 if (x < 0 || z < 0 || x >= size || z >= size) { 254 store.set(Vector2f.ZERO); 255 return store; 256 } 257 int idx = (int) (z * size + x); 258 return store.set(getMesh().getFloatBuffer(Type.TexCoord).get(idx*2), 259 getMesh().getFloatBuffer(Type.TexCoord).get(idx*2+1) ); 260 } 261 262 public float getHeightmapHeight(float x, float z) { 263 if (x < 0 || z < 0 || x >= size || z >= size) 264 return 0; 265 int idx = (int) (z * size + x); 266 return getMesh().getFloatBuffer(Type.Position).get(idx*3+1); // 3 floats per entry (x,y,z), the +1 is to get the Y 267 } 268 269 /** 270 * Get the triangle of this geometry at the specified local coordinate. 271 * @param x local to the terrain patch 272 * @param z local to the terrain patch 273 * @return the triangle in world coordinates, or null if the point does intersect this patch on the XZ axis 274 */ 275 public Triangle getTriangle(float x, float z) { 276 return geomap.getTriangleAtPoint(x, z, getWorldScale() , getWorldTranslation()); 277 } 278 279 /** 280 * Get the triangles at the specified grid point. Probably only 2 triangles 281 * @param x local to the terrain patch 282 * @param z local to the terrain patch 283 * @return the triangles in world coordinates, or null if the point does intersect this patch on the XZ axis 284 */ 285 public Triangle[] getGridTriangles(float x, float z) { 286 return geomap.getGridTrianglesAtPoint(x, z, getWorldScale() , getWorldTranslation()); 287 } 288 289 protected void setHeight(List<LocationHeight> locationHeights, boolean overrideHeight) { 290 291 for (LocationHeight lh : locationHeights) { 292 if (lh.x < 0 || lh.z < 0 || lh.x >= size || lh.z >= size) 293 continue; 294 int idx = lh.z * size + lh.x; 295 if (overrideHeight) { 296 geomap.getHeightArray()[idx] = lh.h; 297 } else { 298 float h = getMesh().getFloatBuffer(Type.Position).get(idx*3+1); 299 geomap.getHeightArray()[idx] = h+lh.h; 300 } 301 302 } 303 304 FloatBuffer newVertexBuffer = geomap.writeVertexArray(null, stepScale, false); 305 getMesh().clearBuffer(Type.Position); 306 getMesh().setBuffer(Type.Position, 3, newVertexBuffer); 307 } 308 309 /** 310 * recalculate all of the normal vectors in this terrain patch 311 */ 312 protected void updateNormals() { 313 FloatBuffer newNormalBuffer = geomap.writeNormalArray(null, getWorldScale()); 314 getMesh().getBuffer(Type.Normal).updateData(newNormalBuffer); 315 FloatBuffer newTangentBuffer = null; 316 FloatBuffer newBinormalBuffer = null; 317 FloatBuffer[] tb = geomap.writeTangentArray(newNormalBuffer, newTangentBuffer, newBinormalBuffer, (FloatBuffer)getMesh().getBuffer(Type.TexCoord).getData(), getWorldScale()); 318 newTangentBuffer = tb[0]; 319 newBinormalBuffer = tb[1]; 320 getMesh().getBuffer(Type.Tangent).updateData(newTangentBuffer); 321 getMesh().getBuffer(Type.Binormal).updateData(newBinormalBuffer); 322 } 323 324 private void setInBuffer(Mesh mesh, int index, Vector3f normal, Vector3f tangent, Vector3f binormal) { 325 VertexBuffer NB = mesh.getBuffer(Type.Normal); 326 VertexBuffer TB = mesh.getBuffer(Type.Tangent); 327 VertexBuffer BB = mesh.getBuffer(Type.Binormal); 328 BufferUtils.setInBuffer(normal, (FloatBuffer)NB.getData(), index); 329 BufferUtils.setInBuffer(tangent, (FloatBuffer)TB.getData(), index); 330 BufferUtils.setInBuffer(binormal, (FloatBuffer)BB.getData(), index); 331 NB.setUpdateNeeded(); 332 TB.setUpdateNeeded(); 333 BB.setUpdateNeeded(); 334 } 335 336 /** 337 * Matches the normals along the edge of the patch with the neighbours. 338 * Computes the normals for the right, bottom, left, and top edges of the 339 * patch, and saves those normals in the neighbour's edges too. 340 * 341 * Takes 4 points (if has neighbour on that side) for each 342 * point on the edge of the patch: 343 * * 344 * | 345 * *---x---* 346 * | 347 * * 348 * It works across the right side of the patch, from the top down to 349 * the bottom. Then it works on the bottom side of the patch, from the 350 * left to the right. 351 */ 352 protected void fixNormalEdges(TerrainPatch right, 353 TerrainPatch bottom, 354 TerrainPatch top, 355 TerrainPatch left, 356 TerrainPatch bottomRight, 357 TerrainPatch bottomLeft, 358 TerrainPatch topRight, 359 TerrainPatch topLeft) 360 { 361 Vector3f rootPoint = new Vector3f(); 362 Vector3f rightPoint = new Vector3f(); 363 Vector3f leftPoint = new Vector3f(); 364 Vector3f topPoint = new Vector3f(); 365 366 Vector3f bottomPoint = new Vector3f(); 367 368 Vector3f tangent = new Vector3f(); 369 Vector3f binormal = new Vector3f(); 370 Vector3f normal = new Vector3f(); 371 372 373 int s = this.getSize()-1; 374 375 if (right != null) { // right side, works its way down 376 for (int i=0; i<s+1; i++) { 377 rootPoint.set(0, this.getHeightmapHeight(s,i), 0); 378 leftPoint.set(-1, this.getHeightmapHeight(s-1,i), 0); 379 rightPoint.set(1, right.getHeightmapHeight(1,i), 0); 380 381 if (i == 0) { // top point 382 bottomPoint.set(0, this.getHeightmapHeight(s,i+1), 1); 383 384 if (top == null) { 385 averageNormalsTangents(null, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal); 386 setInBuffer(this.getMesh(), s, normal, tangent, binormal); 387 setInBuffer(right.getMesh(), 0, normal, tangent, binormal); 388 } else { 389 topPoint.set(0, top.getHeightmapHeight(s,s-1), -1); 390 391 averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint,normal, tangent, binormal); 392 setInBuffer(this.getMesh(), s, normal, tangent, binormal); 393 setInBuffer(right.getMesh(), 0, normal, tangent, binormal); 394 setInBuffer(top.getMesh(), (s+1)*(s+1)-1, normal, tangent, binormal); 395 396 if (topRight != null) { 397 // setInBuffer(topRight.getMesh(), (s+1)*s, normal, tangent, binormal); 398 } 399 } 400 } else if (i == s) { // bottom point 401 topPoint.set(0, this.getHeightmapHeight(s,s-1), -1); 402 403 if (bottom == null) { 404 averageNormalsTangents(topPoint, rootPoint, leftPoint, null, rightPoint, normal, tangent, binormal); 405 setInBuffer(this.getMesh(), (s+1)*(s+1)-1, normal, tangent, binormal); 406 setInBuffer(right.getMesh(), (s+1)*(s), normal, tangent, binormal); 407 } else { 408 bottomPoint.set(0, bottom.getHeightmapHeight(s,1), 1); 409 averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal); 410 setInBuffer(this.getMesh(), (s+1)*(s+1)-1, normal, tangent, binormal); 411 setInBuffer(right.getMesh(), (s+1)*s, normal, tangent, binormal); 412 setInBuffer(bottom.getMesh(), s, normal, tangent, binormal); 413 414 if (bottomRight != null) { 415 // setInBuffer(bottomRight.getMesh(), 0, normal, tangent, binormal); 416 } 417 } 418 } else { // all in the middle 419 topPoint.set(0, this.getHeightmapHeight(s,i-1), -1); 420 bottomPoint.set(0, this.getHeightmapHeight(s,i+1), 1); 421 averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal); 422 setInBuffer(this.getMesh(), (s+1)*(i+1)-1, normal, tangent, binormal); 423 setInBuffer(right.getMesh(), (s+1)*(i), normal, tangent, binormal); 424 } 425 } 426 } 427 428 if (left != null) { // left side, works its way down 429 for (int i=0; i<s+1; i++) { 430 rootPoint.set(0, this.getHeightmapHeight(0,i), 0); 431 leftPoint.set(-1, left.getHeightmapHeight(s-1,i), 0); 432 rightPoint.set(1, this.getHeightmapHeight(1,i), 0); 433 434 if (i == 0) { // top point 435 bottomPoint.set(0, this.getHeightmapHeight(0,i+1), 1); 436 437 if (top == null) { 438 averageNormalsTangents(null, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal); 439 setInBuffer(this.getMesh(), 0, normal, tangent, binormal); 440 setInBuffer(left.getMesh(), s, normal, tangent, binormal); 441 } else { 442 topPoint.set(0, top.getHeightmapHeight(0,s-1), -1); 443 444 averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal); 445 setInBuffer(this.getMesh(), 0, normal, tangent, binormal); 446 setInBuffer(left.getMesh(), s, normal, tangent, binormal); 447 setInBuffer(top.getMesh(), (s+1)*s, normal, tangent, binormal); 448 449 if (topLeft != null) { 450 // setInBuffer(topLeft.getMesh(), (s+1)*(s+1)-1, normal, tangent, binormal); 451 } 452 } 453 } else if (i == s) { // bottom point 454 topPoint.set(0, this.getHeightmapHeight(0,i-1), -1); 455 456 if (bottom == null) { 457 averageNormalsTangents(topPoint, rootPoint, leftPoint, null, rightPoint, normal, tangent, binormal); 458 setInBuffer(this.getMesh(), (s+1)*(s), normal, tangent, binormal); 459 setInBuffer(left.getMesh(), (s+1)*(s+1)-1, normal, tangent, binormal); 460 } else { 461 bottomPoint.set(0, bottom.getHeightmapHeight(0,1), 1); 462 463 averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal); 464 setInBuffer(this.getMesh(), (s+1)*(s), normal, tangent, binormal); 465 setInBuffer(left.getMesh(), (s+1)*(s+1)-1, normal, tangent, binormal); 466 setInBuffer(bottom.getMesh(), 0, normal, tangent, binormal); 467 468 if (bottomLeft != null) { 469 // setInBuffer(bottomLeft.getMesh(), s, normal, tangent, binormal); 470 } 471 } 472 } else { // all in the middle 473 topPoint.set(0, this.getHeightmapHeight(0,i-1), -1); 474 bottomPoint.set(0, this.getHeightmapHeight(0,i+1), 1); 475 476 averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal); 477 setInBuffer(this.getMesh(), (s+1)*(i), normal, tangent, binormal); 478 setInBuffer(left.getMesh(), (s+1)*(i+1)-1, normal, tangent, binormal); 479 } 480 } 481 } 482 483 if (top != null) { // top side, works its way right 484 for (int i=0; i<s+1; i++) { 485 rootPoint.set(0, this.getHeightmapHeight(i,0), 0); 486 topPoint.set(0, top.getHeightmapHeight(i,s-1), -1); 487 bottomPoint.set(0, this.getHeightmapHeight(i,1), 1); 488 489 if (i == 0) { // left corner 490 // handled by left side pass 491 492 } else if (i == s) { // right corner 493 494 // handled by this patch when it does its right side 495 496 } else { // all in the middle 497 leftPoint.set(-1, this.getHeightmapHeight(i-1,0), 0); 498 rightPoint.set(1, this.getHeightmapHeight(i+1,0), 0); 499 averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal); 500 setInBuffer(this.getMesh(), i, normal, tangent, binormal); 501 setInBuffer(top.getMesh(), (s+1)*(s)+i, normal, tangent, binormal); 502 } 503 } 504 505 } 506 507 if (bottom != null) { // bottom side, works its way right 508 for (int i=0; i<s+1; i++) { 509 rootPoint.set(0, this.getHeightmapHeight(i,s), 0); 510 topPoint.set(0, this.getHeightmapHeight(i,s-1), -1); 511 bottomPoint.set(0, bottom.getHeightmapHeight(i,1), 1); 512 513 if (i == 0) { // left 514 // handled by the left side pass 515 516 } else if (i == s) { // right 517 518 // handled by the right side pass 519 520 } else { // all in the middle 521 leftPoint.set(-1, this.getHeightmapHeight(i-1,s), 0); 522 rightPoint.set(1, this.getHeightmapHeight(i+1,s), 0); 523 averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal); 524 setInBuffer(this.getMesh(), (s+1)*(s)+i, normal, tangent, binormal); 525 setInBuffer(bottom.getMesh(), i, normal, tangent, binormal); 526 } 527 } 528 529 } 530 } 531 532 protected void averageNormalsTangents( 533 Vector3f topPoint, 534 Vector3f rootPoint, 535 Vector3f leftPoint, 536 Vector3f bottomPoint, 537 Vector3f rightPoint, 538 Vector3f normal, 539 Vector3f tangent, 540 Vector3f binormal) 541 { 542 Vector3f scale = getWorldScale(); 543 544 Vector3f n1 = new Vector3f(0,0,0); 545 if (topPoint != null && leftPoint != null) { 546 n1.set(calculateNormal(topPoint.mult(scale), rootPoint.mult(scale), leftPoint.mult(scale))); 547 } 548 Vector3f n2 = new Vector3f(0,0,0); 549 if (leftPoint != null && bottomPoint != null) { 550 n2.set(calculateNormal(leftPoint.mult(scale), rootPoint.mult(scale), bottomPoint.mult(scale))); 551 } 552 Vector3f n3 = new Vector3f(0,0,0); 553 if (rightPoint != null && bottomPoint != null) { 554 n3.set(calculateNormal(bottomPoint.mult(scale), rootPoint.mult(scale), rightPoint.mult(scale))); 555 } 556 Vector3f n4 = new Vector3f(0,0,0); 557 if (rightPoint != null && topPoint != null) { 558 n4.set(calculateNormal(rightPoint.mult(scale), rootPoint.mult(scale), topPoint.mult(scale))); 559 } 560 561 //if (bottomPoint != null && rightPoint != null && rootTex != null && rightTex != null && bottomTex != null) 562 // LODGeomap.calculateTangent(new Vector3f[]{rootPoint.mult(scale),rightPoint.mult(scale),bottomPoint.mult(scale)}, new Vector2f[]{rootTex,rightTex,bottomTex}, tangent, binormal); 563 564 normal.set(n1.add(n2).add(n3).add(n4).normalize()); 565 566 tangent.set(normal.cross(new Vector3f(0,0,1)).normalize()); 567 binormal.set(new Vector3f(1,0,0).cross(normal).normalize()); 568 } 569 570 private Vector3f calculateNormal(Vector3f firstPoint, Vector3f rootPoint, Vector3f secondPoint) { 571 Vector3f normal = new Vector3f(); 572 normal.set(firstPoint).subtractLocal(rootPoint) 573 .crossLocal(secondPoint.subtract(rootPoint)).normalizeLocal(); 574 return normal; 575 } 576 577 protected Vector3f getMeshNormal(int x, int z) { 578 if (x >= size || z >= size) 579 return null; // out of range 580 581 int index = (z*size+x)*3; 582 FloatBuffer nb = (FloatBuffer)this.getMesh().getBuffer(Type.Normal).getData(); 583 Vector3f normal = new Vector3f(); 584 normal.x = nb.get(index); 585 normal.y = nb.get(index+1); 586 normal.z = nb.get(index+2); 587 return normal; 588 } 589 590 /** 591 * Locks the mesh (sets it static) to improve performance. 592 * But it it not editable then. Set unlock to make it editable. 593 */ 594 public void lockMesh() { 595 getMesh().setStatic(); 596 } 597 598 /** 599 * Unlocks the mesh (sets it dynamic) to make it editable. 600 * It will be editable but performance will be reduced. 601 * Call lockMesh to improve performance. 602 */ 603 public void unlockMesh() { 604 getMesh().setDynamic(); 605 } 606 607 /** 608 * Returns the offset amount this terrain patch uses for textures. 609 * 610 * @return The current offset amount. 611 */ 612 public float getOffsetAmount() { 613 return offsetAmount; 614 } 615 616 /** 617 * Returns the step scale that stretches the height map. 618 * 619 * @return The current step scale. 620 */ 621 public Vector3f getStepScale() { 622 return stepScale; 623 } 624 625 /** 626 * Returns the total size of the terrain. 627 * 628 * @return The terrain's total size. 629 */ 630 public int getTotalSize() { 631 return totalSize; 632 } 633 634 /** 635 * Returns the size of this terrain patch. 636 * 637 * @return The current patch size. 638 */ 639 public int getSize() { 640 return size; 641 } 642 643 /** 644 * Returns the current offset amount. This is used when building texture 645 * coordinates. 646 * 647 * @return The current offset amount. 648 */ 649 public Vector2f getOffset() { 650 return offset; 651 } 652 653 /** 654 * Sets the value for the current offset amount to use when building texture 655 * coordinates. Note that this does <b>NOT </b> rebuild the terrain at all. 656 * This is mostly used for outside constructors of terrain patches. 657 * 658 * @param offset 659 * The new texture offset. 660 */ 661 public void setOffset(Vector2f offset) { 662 this.offset = offset; 663 } 664 665 /** 666 * Sets the size of this terrain patch. Note that this does <b>NOT </b> 667 * rebuild the terrain at all. This is mostly used for outside constructors 668 * of terrain patches. 669 * 670 * @param size 671 * The new size. 672 */ 673 public void setSize(int size) { 674 this.size = size; 675 676 maxLod = -1; // reset it 677 } 678 679 /** 680 * Sets the total size of the terrain . Note that this does <b>NOT </b> 681 * rebuild the terrain at all. This is mostly used for outside constructors 682 * of terrain patches. 683 * 684 * @param totalSize 685 * The new total size. 686 */ 687 public void setTotalSize(int totalSize) { 688 this.totalSize = totalSize; 689 } 690 691 /** 692 * Sets the step scale of this terrain patch's height map. Note that this 693 * does <b>NOT </b> rebuild the terrain at all. This is mostly used for 694 * outside constructors of terrain patches. 695 * 696 * @param stepScale 697 * The new step scale. 698 */ 699 public void setStepScale(Vector3f stepScale) { 700 this.stepScale = stepScale; 701 } 702 703 /** 704 * Sets the offset of this terrain texture map. Note that this does <b>NOT 705 * </b> rebuild the terrain at all. This is mostly used for outside 706 * constructors of terrain patches. 707 * 708 * @param offsetAmount 709 * The new texture offset. 710 */ 711 public void setOffsetAmount(float offsetAmount) { 712 this.offsetAmount = offsetAmount; 713 } 714 715 /** 716 * @return Returns the quadrant. 717 */ 718 public short getQuadrant() { 719 return quadrant; 720 } 721 722 /** 723 * @param quadrant 724 * The quadrant to set. 725 */ 726 public void setQuadrant(short quadrant) { 727 this.quadrant = quadrant; 728 } 729 730 public int getLod() { 731 return lod; 732 } 733 734 public void setLod(int lod) { 735 this.lod = lod; 736 } 737 738 public int getPreviousLod() { 739 return previousLod; 740 } 741 742 public void setPreviousLod(int previousLod) { 743 this.previousLod = previousLod; 744 } 745 746 protected int getLodLeft() { 747 return lodLeft; 748 } 749 750 protected void setLodLeft(int lodLeft) { 751 this.lodLeft = lodLeft; 752 } 753 754 protected int getLodTop() { 755 return lodTop; 756 } 757 758 protected void setLodTop(int lodTop) { 759 this.lodTop = lodTop; 760 } 761 762 protected int getLodRight() { 763 return lodRight; 764 } 765 766 protected void setLodRight(int lodRight) { 767 this.lodRight = lodRight; 768 } 769 770 protected int getLodBottom() { 771 return lodBottom; 772 } 773 774 protected void setLodBottom(int lodBottom) { 775 this.lodBottom = lodBottom; 776 } 777 778 /*public void setLodCalculator(LodCalculatorFactory lodCalculatorFactory) { 779 this.lodCalculatorFactory = lodCalculatorFactory; 780 setLodCalculator(lodCalculatorFactory.createCalculator(this)); 781 }*/ 782 783 @Override 784 public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException { 785 if (refreshFlags != 0) 786 throw new IllegalStateException("Scene graph must be updated" + 787 " before checking collision"); 788 789 if (other instanceof BoundingVolume) 790 if (!getWorldBound().intersects((BoundingVolume)other)) 791 return 0; 792 793 if(other instanceof Ray) 794 return collideWithRay((Ray)other, results); 795 else if (other instanceof BoundingVolume) 796 return collideWithBoundingVolume((BoundingVolume)other, results); 797 else { 798 throw new UnsupportedCollisionException("TerrainPatch cannnot collide with "+other.getClass().getName()); 799 } 800 } 801 802 803 private int collideWithRay(Ray ray, CollisionResults results) { 804 // This should be handled in the root terrain quad 805 return 0; 806 } 807 808 private int collideWithBoundingVolume(BoundingVolume boundingVolume, CollisionResults results) { 809 if (boundingVolume instanceof BoundingBox) 810 return collideWithBoundingBox((BoundingBox)boundingVolume, results); 811 else if(boundingVolume instanceof BoundingSphere) { 812 BoundingSphere sphere = (BoundingSphere) boundingVolume; 813 BoundingBox bbox = new BoundingBox(boundingVolume.getCenter().clone(), sphere.getRadius(), 814 sphere.getRadius(), 815 sphere.getRadius()); 816 return collideWithBoundingBox(bbox, results); 817 } 818 return 0; 819 } 820 821 protected Vector3f worldCoordinateToLocal(Vector3f loc) { 822 Vector3f translated = new Vector3f(); 823 translated.x = loc.x/getWorldScale().x - getWorldTranslation().x; 824 translated.y = loc.y/getWorldScale().y - getWorldTranslation().y; 825 translated.z = loc.z/getWorldScale().z - getWorldTranslation().z; 826 return translated; 827 } 828 829 /** 830 * This most definitely is not optimized. 831 */ 832 private int collideWithBoundingBox(BoundingBox bbox, CollisionResults results) { 833 834 // test the four corners, for cases where the bbox dimensions are less than the terrain grid size, which is probably most of the time 835 Vector3f topLeft = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x-bbox.getXExtent(), 0, bbox.getCenter().z-bbox.getZExtent())); 836 Vector3f topRight = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x+bbox.getXExtent(), 0, bbox.getCenter().z-bbox.getZExtent())); 837 Vector3f bottomLeft = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x-bbox.getXExtent(), 0, bbox.getCenter().z+bbox.getZExtent())); 838 Vector3f bottomRight = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x+bbox.getXExtent(), 0, bbox.getCenter().z+bbox.getZExtent())); 839 840 Triangle t = getTriangle(topLeft.x, topLeft.z); 841 if (t != null && bbox.collideWith(t, results) > 0) 842 return 1; 843 t = getTriangle(topRight.x, topRight.z); 844 if (t != null && bbox.collideWith(t, results) > 0) 845 return 1; 846 t = getTriangle(bottomLeft.x, bottomLeft.z); 847 if (t != null && bbox.collideWith(t, results) > 0) 848 return 1; 849 t = getTriangle(bottomRight.x, bottomRight.z); 850 if (t != null && bbox.collideWith(t, results) > 0) 851 return 1; 852 853 // box is larger than the points on the terrain, so test against the points 854 for (float z=topLeft.z; z<bottomLeft.z; z+=1) { 855 for (float x=topLeft.x; x<topRight.x; x+=1) { 856 857 if (x < 0 || z < 0 || x >= size || z >= size) 858 continue; 859 t = getTriangle(x,z); 860 if (t != null && bbox.collideWith(t, results) > 0) 861 return 1; 862 } 863 } 864 865 return 0; 866 } 867 868 869 @Override 870 public void write(JmeExporter ex) throws IOException { 871 // the mesh is removed, and reloaded when read() is called 872 // this reduces the save size to 10% by not saving the mesh 873 Mesh temp = getMesh(); 874 mesh = null; 875 876 super.write(ex); 877 OutputCapsule oc = ex.getCapsule(this); 878 oc.write(size, "size", 16); 879 oc.write(totalSize, "totalSize", 16); 880 oc.write(quadrant, "quadrant", (short)0); 881 oc.write(stepScale, "stepScale", Vector3f.UNIT_XYZ); 882 oc.write(offset, "offset", Vector3f.UNIT_XYZ); 883 oc.write(offsetAmount, "offsetAmount", 0); 884 //oc.write(lodCalculator, "lodCalculator", null); 885 //oc.write(lodCalculatorFactory, "lodCalculatorFactory", null); 886 oc.write(lodEntropy, "lodEntropy", null); 887 oc.write(geomap, "geomap", null); 888 889 setMesh(temp); 890 } 891 892 @Override 893 public void read(JmeImporter im) throws IOException { 894 super.read(im); 895 InputCapsule ic = im.getCapsule(this); 896 size = ic.readInt("size", 16); 897 totalSize = ic.readInt("totalSize", 16); 898 quadrant = ic.readShort("quadrant", (short)0); 899 stepScale = (Vector3f) ic.readSavable("stepScale", Vector3f.UNIT_XYZ); 900 offset = (Vector2f) ic.readSavable("offset", Vector3f.UNIT_XYZ); 901 offsetAmount = ic.readFloat("offsetAmount", 0); 902 //lodCalculator = (LodCalculator) ic.readSavable("lodCalculator", new DistanceLodCalculator()); 903 //lodCalculator.setTerrainPatch(this); 904 //lodCalculatorFactory = (LodCalculatorFactory) ic.readSavable("lodCalculatorFactory", null); 905 lodEntropy = ic.readFloatArray("lodEntropy", null); 906 geomap = (LODGeomap) ic.readSavable("geomap", null); 907 908 Mesh regen = geomap.createMesh(stepScale, new Vector2f(1,1), offset, offsetAmount, totalSize, false); 909 setMesh(regen); 910 //TangentBinormalGenerator.generate(this); // note that this will be removed 911 ensurePositiveVolumeBBox(); 912 } 913 914 @Override 915 public TerrainPatch clone() { 916 TerrainPatch clone = new TerrainPatch(); 917 clone.name = name.toString(); 918 clone.size = size; 919 clone.totalSize = totalSize; 920 clone.quadrant = quadrant; 921 clone.stepScale = stepScale.clone(); 922 clone.offset = offset.clone(); 923 clone.offsetAmount = offsetAmount; 924 //clone.lodCalculator = lodCalculator.clone(); 925 //clone.lodCalculator.setTerrainPatch(clone); 926 //clone.setLodCalculator(lodCalculatorFactory.clone()); 927 clone.geomap = new LODGeomap(size, geomap.getHeightArray()); 928 clone.setLocalTranslation(getLocalTranslation().clone()); 929 Mesh m = clone.geomap.createMesh(clone.stepScale, Vector2f.UNIT_XY, clone.offset, clone.offsetAmount, clone.totalSize, false); 930 clone.setMesh(m); 931 clone.setMaterial(material.clone()); 932 return clone; 933 } 934 935 protected void ensurePositiveVolumeBBox() { 936 if (getModelBound() instanceof BoundingBox) { 937 if (((BoundingBox)getModelBound()).getYExtent() < 0.001f) { 938 // a correction so the box always has a volume 939 ((BoundingBox)getModelBound()).setYExtent(0.001f); 940 updateWorldBound(); 941 } 942 } 943 } 944 945 946 947} 948