• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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