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