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 33 package com.jme3.terrain.geomipmap; 34 35 import com.jme3.bounding.BoundingBox; 36 import com.jme3.bounding.BoundingSphere; 37 import com.jme3.bounding.BoundingVolume; 38 import com.jme3.collision.Collidable; 39 import com.jme3.collision.CollisionResults; 40 import com.jme3.collision.UnsupportedCollisionException; 41 import com.jme3.export.InputCapsule; 42 import com.jme3.export.JmeExporter; 43 import com.jme3.export.JmeImporter; 44 import com.jme3.export.OutputCapsule; 45 import com.jme3.math.*; 46 import com.jme3.scene.Geometry; 47 import com.jme3.scene.Mesh; 48 import com.jme3.scene.VertexBuffer; 49 import com.jme3.scene.VertexBuffer.Type; 50 import com.jme3.terrain.geomipmap.TerrainQuad.LocationHeight; 51 import com.jme3.terrain.geomipmap.lodcalc.util.EntropyComputeUtil; 52 import com.jme3.util.BufferUtils; 53 import java.io.IOException; 54 import java.nio.FloatBuffer; 55 import java.nio.IntBuffer; 56 import java.util.HashMap; 57 import 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 */ 80 public 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 TerrainPatch()112 public TerrainPatch() { 113 super("TerrainPatch"); 114 } 115 TerrainPatch(String name)116 public TerrainPatch(String name) { 117 super(name); 118 } 119 TerrainPatch(String name, int size)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 */ TerrainPatch(String name, int size, Vector3f stepScale, float[] heightMap, Vector3f origin)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 */ TerrainPatch(String name, int size, Vector3f stepScale, float[] heightMap, Vector3f origin, int totalSize, Vector2f offset, float offsetAmount)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 */ generateLodEntropies()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 getLodEntropies()200 public float[] getLodEntropies(){ 201 if (lodEntropy == null){ 202 generateLodEntropies(); 203 } 204 return lodEntropy; 205 } 206 207 @Deprecated getHeightmap()208 public FloatBuffer getHeightmap() { 209 return BufferUtils.createFloatBuffer(geomap.getHeightArray()); 210 } 211 getHeightMap()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 */ getMaxLod()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 reIndexGeometry(HashMap<String,UpdatedTerrainPatch> updated, boolean useVariableLod)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 getTex(float x, float z, Vector2f store)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 getHeightmapHeight(float x, float z)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 */ getTriangle(float x, float z)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 */ getGridTriangles(float x, float z)285 public Triangle[] getGridTriangles(float x, float z) { 286 return geomap.getGridTrianglesAtPoint(x, z, getWorldScale() , getWorldTranslation()); 287 } 288 setHeight(List<LocationHeight> locationHeights, boolean overrideHeight)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 */ updateNormals()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 setInBuffer(Mesh mesh, int index, Vector3f normal, Vector3f tangent, Vector3f binormal)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 */ fixNormalEdges(TerrainPatch right, TerrainPatch bottom, TerrainPatch top, TerrainPatch left, TerrainPatch bottomRight, TerrainPatch bottomLeft, TerrainPatch topRight, TerrainPatch topLeft)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 averageNormalsTangents( Vector3f topPoint, Vector3f rootPoint, Vector3f leftPoint, Vector3f bottomPoint, Vector3f rightPoint, Vector3f normal, Vector3f tangent, Vector3f binormal)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 calculateNormal(Vector3f firstPoint, Vector3f rootPoint, Vector3f secondPoint)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 getMeshNormal(int x, int z)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 */ lockMesh()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 */ unlockMesh()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 */ getOffsetAmount()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 */ getStepScale()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 */ getTotalSize()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 */ getSize()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 */ getOffset()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 */ setOffset(Vector2f offset)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 */ setSize(int size)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 */ setTotalSize(int totalSize)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 */ setStepScale(Vector3f stepScale)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 */ setOffsetAmount(float offsetAmount)711 public void setOffsetAmount(float offsetAmount) { 712 this.offsetAmount = offsetAmount; 713 } 714 715 /** 716 * @return Returns the quadrant. 717 */ getQuadrant()718 public short getQuadrant() { 719 return quadrant; 720 } 721 722 /** 723 * @param quadrant 724 * The quadrant to set. 725 */ setQuadrant(short quadrant)726 public void setQuadrant(short quadrant) { 727 this.quadrant = quadrant; 728 } 729 getLod()730 public int getLod() { 731 return lod; 732 } 733 setLod(int lod)734 public void setLod(int lod) { 735 this.lod = lod; 736 } 737 getPreviousLod()738 public int getPreviousLod() { 739 return previousLod; 740 } 741 setPreviousLod(int previousLod)742 public void setPreviousLod(int previousLod) { 743 this.previousLod = previousLod; 744 } 745 getLodLeft()746 protected int getLodLeft() { 747 return lodLeft; 748 } 749 setLodLeft(int lodLeft)750 protected void setLodLeft(int lodLeft) { 751 this.lodLeft = lodLeft; 752 } 753 getLodTop()754 protected int getLodTop() { 755 return lodTop; 756 } 757 setLodTop(int lodTop)758 protected void setLodTop(int lodTop) { 759 this.lodTop = lodTop; 760 } 761 getLodRight()762 protected int getLodRight() { 763 return lodRight; 764 } 765 setLodRight(int lodRight)766 protected void setLodRight(int lodRight) { 767 this.lodRight = lodRight; 768 } 769 getLodBottom()770 protected int getLodBottom() { 771 return lodBottom; 772 } 773 setLodBottom(int lodBottom)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 collideWith(Collidable other, CollisionResults results)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 collideWithRay(Ray ray, CollisionResults results)803 private int collideWithRay(Ray ray, CollisionResults results) { 804 // This should be handled in the root terrain quad 805 return 0; 806 } 807 collideWithBoundingVolume(BoundingVolume boundingVolume, CollisionResults results)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 worldCoordinateToLocal(Vector3f loc)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 */ collideWithBoundingBox(BoundingBox bbox, CollisionResults results)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 write(JmeExporter ex)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 read(JmeImporter im)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 clone()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 ensurePositiveVolumeBBox()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