001/**
002 * Copyright (c) 2008 Ardor Labs, Inc.
003 *
004 * This file is part of Ardor3D.
005 *
006 * Ardor3D is free software: you can redistribute it and/or modify it
007 * under the terms of its license which may be found in the accompanying
008 * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
009 */
010
011package com.ardor3d.extension.effect.water;
012
013import java.nio.FloatBuffer;
014import java.util.Stack;
015import java.util.concurrent.ExecutionException;
016import java.util.concurrent.ExecutorService;
017import java.util.concurrent.Executors;
018import java.util.concurrent.Future;
019import java.util.concurrent.ThreadFactory;
020import java.util.concurrent.atomic.AtomicInteger;
021import java.util.logging.Level;
022import java.util.logging.Logger;
023
024import com.ardor3d.math.ColorRGBA;
025import com.ardor3d.math.MathUtils;
026import com.ardor3d.math.Matrix4;
027import com.ardor3d.math.Vector2;
028import com.ardor3d.math.Vector3;
029import com.ardor3d.math.Vector4;
030import com.ardor3d.math.type.ReadOnlyMatrix4;
031import com.ardor3d.math.type.ReadOnlyVector2;
032import com.ardor3d.math.type.ReadOnlyVector3;
033import com.ardor3d.renderer.Camera;
034import com.ardor3d.renderer.Renderer;
035import com.ardor3d.scenegraph.IndexBufferData;
036import com.ardor3d.scenegraph.Mesh;
037import com.ardor3d.util.ExtendedCamera;
038import com.ardor3d.util.Timer;
039import com.ardor3d.util.geom.BufferUtils;
040import com.ardor3d.util.geom.Debugger;
041
042/**
043 * <code>ProjectedGrid</code> Projected grid mesh
044 */
045public class ProjectedGrid extends Mesh {
046    /** The Constant logger. */
047    private static final Logger logger = Logger.getLogger(ProjectedGrid.class.getName());
048
049    private final int sizeX;
050    private final int sizeY;
051
052    private FloatBuffer vertBuf;
053    private final FloatBuffer normBuf;
054    private final FloatBuffer texs;
055
056    private final ExtendedCamera mainCamera = new ExtendedCamera();
057    private final Camera projectorCamera = new Camera();
058
059    private final Vector4 origin = new Vector4();
060    private final Vector4 direction = new Vector4();
061    private final Vector2 source = new Vector2();
062    private final Matrix4 rangeMatrix = new Matrix4();
063
064    private final Vector4 intersectBottomLeft = new Vector4();
065    private final Vector4 intersectTopLeft = new Vector4();
066    private final Vector4 intersectTopRight = new Vector4();
067    private final Vector4 intersectBottomRight = new Vector4();
068
069    private final Vector3 planeIntersection = new Vector3();
070
071    public boolean freezeProjector = false;
072    private final Timer timer;
073    private final Camera camera;
074
075    private final HeightGenerator heightGenerator;
076    private final float textureScale;
077
078    private double projectorMinHeight = 100.0;
079    private final Vector3[] intersections = new Vector3[24];
080
081    private final float[] vertBufArray;
082    private final float[] normBufArray;
083    private final float[] texBufArray;
084
085    private int nrUpdateThreads = 1;
086    private final ExecutorService executorService = Executors.newCachedThreadPool(new DeamonThreadFactory());
087    private final Stack<Future<?>> futureStack = new Stack<>();
088
089    private final int connections[] = { 0, 1, 2, 3, 0, 4, 1, 5, 2, 6, 3, 7, 4, 5, 6, 7, };
090
091    // Debug drawing
092    private boolean drawDebug = false;
093
094    public ProjectedGrid(final String name, final Camera camera, final int sizeX, final int sizeY,
095            final float textureScale, final HeightGenerator heightGenerator, final Timer timer) {
096        super(name);
097        this.sizeX = sizeX;
098        this.sizeY = sizeY;
099        this.textureScale = textureScale;
100        this.heightGenerator = heightGenerator;
101        this.camera = camera;
102        this.timer = timer;
103
104        buildVertices(sizeX * sizeY);
105        texs = BufferUtils.createVector2Buffer(_meshData.getVertexCount());
106        _meshData.setTextureBuffer(texs, 0);
107        normBuf = BufferUtils.createVector3Buffer(_meshData.getVertexCount());
108        _meshData.setNormalBuffer(normBuf);
109
110        vertBufArray = new float[_meshData.getVertexCount() * 3];
111        normBufArray = new float[_meshData.getVertexCount() * 3];
112        texBufArray = new float[_meshData.getVertexCount() * 2];
113
114        for (int i = 0; i < 24; i++) {
115            intersections[i] = new Vector3();
116        }
117    }
118
119    public void setNrUpdateThreads(final int nrUpdateThreads) {
120        this.nrUpdateThreads = nrUpdateThreads;
121        if (this.nrUpdateThreads < 1) {
122            this.nrUpdateThreads = 1;
123        }
124    }
125
126    public int getNrUpdateThreads() {
127        return nrUpdateThreads;
128    }
129
130    public void setFreezeUpdate(final boolean freeze) {
131        freezeProjector = freeze;
132    }
133
134    public boolean isFreezeUpdate() {
135        return freezeProjector;
136    }
137
138    @Override
139    public void render(final Renderer renderer) {
140        final boolean doDraw = update();
141        if (doDraw) {
142            super.render(renderer);
143        }
144
145        if (drawDebug) {
146            Debugger.drawCameraFrustum(renderer, mainCamera, new ColorRGBA(1, 0, 0, 1), (short) 0xFFFF, true);
147            Debugger.drawCameraFrustum(renderer, projectorCamera, new ColorRGBA(0, 1, 1, 1), (short) 0xFFFF, true);
148        }
149    }
150
151    public boolean update() {
152        final double upperBound = heightGenerator.getMaximumHeight();
153
154        if (!freezeProjector) {
155            mainCamera.set(camera);
156
157            final Vector3 tmp = new Vector3();
158            getWorldTransform().applyInverse(mainCamera.getLocation(), tmp);
159            mainCamera.setLocation(tmp);
160            getWorldTransform().applyInverseVector(mainCamera.getLeft(), tmp);
161            mainCamera.setLeft(tmp);
162            getWorldTransform().applyInverseVector(mainCamera.getUp(), tmp);
163            mainCamera.setUp(tmp);
164            getWorldTransform().applyInverseVector(mainCamera.getDirection(), tmp);
165            mainCamera.setDirection(tmp);
166        }
167
168        final ReadOnlyVector3 mainCameraLocation = mainCamera.getLocation();
169        if (mainCameraLocation.getY() > 0.0 && mainCameraLocation.getY() < upperBound + mainCamera.getFrustumNear()) {
170            mainCamera.setLocation(mainCameraLocation.getX(), upperBound + mainCamera.getFrustumNear(),
171                    mainCameraLocation.getZ());
172        } else if (mainCameraLocation.getY() < 0.0
173                && mainCameraLocation.getY() > -upperBound - mainCamera.getFrustumNear()) {
174            mainCamera.setLocation(mainCameraLocation.getX(), -upperBound - mainCamera.getFrustumNear(),
175                    mainCameraLocation.getZ());
176        }
177        mainCamera.calculateFrustum();
178        final Vector3[] corners = mainCamera.getCorners();
179
180        int nrPoints = 0;
181
182        // check intersections of frustum connections with upper and lower bound
183        final Vector3 tmpStorage = Vector3.fetchTempInstance();
184        for (int i = 0; i < 8; i++) {
185            final int source = connections[i * 2];
186            final int destination = connections[i * 2 + 1];
187
188            if (corners[source].getY() > upperBound && corners[destination].getY() < upperBound
189                    || corners[source].getY() < upperBound && corners[destination].getY() > upperBound) {
190                getWorldIntersection(upperBound, corners[source], corners[destination], intersections[nrPoints++],
191                        tmpStorage);
192            }
193            if (corners[source].getY() > -upperBound && corners[destination].getY() < -upperBound
194                    || corners[source].getY() < -upperBound && corners[destination].getY() > -upperBound) {
195                getWorldIntersection(-upperBound, corners[source], corners[destination], intersections[nrPoints++],
196                        tmpStorage);
197            }
198        }
199        // check if any of the frustums corner vertices lie between the upper and lower bound planes
200        for (int i = 0; i < 8; i++) {
201            if (corners[i].getY() < upperBound && corners[i].getY() > -upperBound) {
202                intersections[nrPoints++].set(corners[i]);
203            }
204        }
205
206        if (nrPoints == 0) {
207            // No intersection, grid not visible
208            return false;
209        }
210
211        // set projector
212        projectorCamera.set(mainCamera);
213
214        // force the projector to point at the plane
215        if (projectorCamera.getLocation().getY() > 0.0 && projectorCamera.getDirection().getY() > 0.0
216                || projectorCamera.getLocation().getY() < 0.0 && projectorCamera.getDirection().getY() < 0.0) {
217            projectorCamera.setDirection(new Vector3(projectorCamera.getDirection().getX(),
218                    -projectorCamera.getDirection().getY(), projectorCamera.getDirection().getZ()));
219            projectorCamera
220                    .setUp(projectorCamera.getDirection().cross(projectorCamera.getLeft(), null).normalizeLocal());
221        }
222
223        // find the plane intersection point
224        source.set(0.5, 0.5);
225        getWorldIntersection(0.0, source, projectorCamera.getModelViewProjectionInverseMatrix(), planeIntersection);
226
227        // force the projector to be a certain distance above the plane
228        final ReadOnlyVector3 cameraLocation = projectorCamera.getLocation();
229        if (cameraLocation.getY() > 0.0 && cameraLocation.getY() < projectorMinHeight * 2) {
230            final double delta = (projectorMinHeight * 2 - cameraLocation.getY()) / (projectorMinHeight * 2);
231
232            projectorCamera.setLocation(cameraLocation.getX(), projectorMinHeight * 2 - projectorMinHeight * delta,
233                    cameraLocation.getZ());
234        } else if (cameraLocation.getY() < 0.0 && cameraLocation.getY() > -projectorMinHeight * 2) {
235            final double delta = (-projectorMinHeight * 2 - cameraLocation.getY()) / (-projectorMinHeight * 2);
236
237            projectorCamera.setLocation(cameraLocation.getX(), -projectorMinHeight * 2 + projectorMinHeight * delta,
238                    cameraLocation.getZ());
239        }
240
241        // restrict the intersection point to be a certain distance from the camera in plane coords
242        planeIntersection.subtractLocal(projectorCamera.getLocation());
243        planeIntersection.setY(0.0);
244        final double length = planeIntersection.length();
245        if (length > Math.abs(projectorCamera.getLocation().getY())) {
246            planeIntersection.normalizeLocal();
247            planeIntersection.multiplyLocal(Math.abs(projectorCamera.getLocation().getY()));
248        } else if (length < MathUtils.EPSILON) {
249            planeIntersection.addLocal(projectorCamera.getUp());
250            planeIntersection.setY(0.0);
251            planeIntersection.normalizeLocal();
252            planeIntersection.multiplyLocal(0.1); // TODO: magic number
253        }
254        planeIntersection.addLocal(projectorCamera.getLocation());
255        planeIntersection.setY(0.0);
256
257        // point projector at the new intersection point
258        projectorCamera.lookAt(planeIntersection, Vector3.UNIT_Y);
259
260        // transform points to projector space
261        final ReadOnlyMatrix4 modelViewProjectionMatrix = projectorCamera.getModelViewProjectionMatrix();
262        final Vector4 spaceTransformation = new Vector4();
263        for (int i = 0; i < nrPoints; i++) {
264            spaceTransformation.set(intersections[i].getX(), 0.0, intersections[i].getZ(), 1.0);
265            modelViewProjectionMatrix.applyPre(spaceTransformation, spaceTransformation);
266            intersections[i].set(spaceTransformation.getX(), spaceTransformation.getY(), 0);
267            intersections[i].divideLocal(spaceTransformation.getW());
268        }
269
270        // find min/max in projector space
271        double minX = Double.MAX_VALUE;
272        double maxX = -Double.MAX_VALUE;
273        double minY = Double.MAX_VALUE;
274        double maxY = -Double.MAX_VALUE;
275        for (int i = 0; i < nrPoints; i++) {
276            if (intersections[i].getX() < minX) {
277                minX = intersections[i].getX();
278            }
279            if (intersections[i].getX() > maxX) {
280                maxX = intersections[i].getX();
281            }
282            if (intersections[i].getY() < minY) {
283                minY = intersections[i].getY();
284            }
285            if (intersections[i].getY() > maxY) {
286                maxY = intersections[i].getY();
287            }
288        }
289
290        // create range matrix
291        rangeMatrix.setIdentity();
292        rangeMatrix.setM00(maxX - minX);
293        rangeMatrix.setM11(maxY - minY);
294        rangeMatrix.setM30(minX);
295        rangeMatrix.setM31(minY);
296
297        final ReadOnlyMatrix4 modelViewProjectionInverseMatrix = projectorCamera.getModelViewProjectionInverseMatrix();
298        rangeMatrix.multiplyLocal(modelViewProjectionInverseMatrix);
299
300        // convert screen coords to homogenous world coords with new range matrix
301        source.set(0.5, 0.5);
302        getWorldIntersectionHomogenous(0.0, source, rangeMatrix, intersectBottomLeft);
303        source.set(0.5, 1);
304        getWorldIntersectionHomogenous(0.0, source, rangeMatrix, intersectTopLeft);
305        source.set(1, 1);
306        getWorldIntersectionHomogenous(0.0, source, rangeMatrix, intersectTopRight);
307        source.set(1, 0.5);
308        getWorldIntersectionHomogenous(0.0, source, rangeMatrix, intersectBottomRight);
309
310        // update data
311        if (nrUpdateThreads <= 1) {
312            updateGrid(0, sizeY);
313        } else {
314            for (int i = 0; i < nrUpdateThreads; i++) {
315                final int from = sizeY * i / (nrUpdateThreads);
316                final int to = sizeY * (i + 1) / (nrUpdateThreads);
317                final Future<?> future = executorService.submit(new Runnable() {
318                    @Override
319                    public void run() {
320                        updateGrid(from, to);
321                    }
322                });
323                futureStack.push(future);
324            }
325            try {
326                while (!futureStack.isEmpty()) {
327                    futureStack.pop().get();
328                }
329            } catch (final InterruptedException ex) {
330                logger.log(Level.SEVERE, "InterruptedException in thread execution", ex);
331            } catch (final ExecutionException ex) {
332                logger.log(Level.SEVERE, "ExecutionException in thread execution", ex);
333            }
334        }
335
336        vertBuf.rewind();
337        vertBuf.put(vertBufArray);
338
339        texs.rewind();
340        texs.put(texBufArray);
341
342        normBuf.rewind();
343        normBuf.put(normBufArray);
344
345        return true;
346    }
347
348    private boolean getWorldIntersection(final double planeHeight, final Vector3 source, final Vector3 destination,
349            final Vector3 store, final Vector3 tmpStorage) {
350        final Vector3 origin = store.set(source);
351        final Vector3 direction = tmpStorage.set(destination).subtractLocal(origin);
352
353        final double t = (planeHeight - origin.getY()) / (direction.getY());
354
355        direction.multiplyLocal(t);
356        origin.addLocal(direction);
357
358        return t >= 0.0 && t <= 1.0;
359    }
360
361    private void updateGrid(final int from, final int to) {
362        final double time = timer.getTimeInSeconds();
363        final double du = 1.0f / (double) (sizeX - 1);
364        final double dv = 1.0f / (double) (sizeY - 1);
365
366        final Vector4 pointTop = Vector4.fetchTempInstance();
367        final Vector4 pointFinal = Vector4.fetchTempInstance();
368        final Vector4 pointBottom = Vector4.fetchTempInstance();
369
370        int smallerFrom = from;
371        if (smallerFrom > 0) {
372            smallerFrom--;
373        }
374        int biggerTo = to;
375        if (biggerTo < sizeY) {
376            biggerTo++;
377        }
378        double u = 0, v = smallerFrom * dv;
379        int index = smallerFrom * sizeX * 3;
380        for (int y = smallerFrom; y < biggerTo; y++) {
381            for (int x = 0; x < sizeX; x++) {
382                pointTop.lerpLocal(intersectTopLeft, intersectTopRight, u);
383                pointBottom.lerpLocal(intersectBottomLeft, intersectBottomRight, u);
384                pointFinal.lerpLocal(pointTop, pointBottom, v);
385
386                pointFinal.setX(pointFinal.getX() / pointFinal.getW());
387                pointFinal.setZ(pointFinal.getZ() / pointFinal.getW());
388                pointFinal.setY(heightGenerator.getHeight(pointFinal.getX(), pointFinal.getZ(), time));
389
390                vertBufArray[index++] = pointFinal.getXf();
391                vertBufArray[index++] = pointFinal.getYf();
392                vertBufArray[index++] = pointFinal.getZf();
393
394                u += du;
395            }
396            v += dv;
397            u = 0;
398        }
399
400        Vector4.releaseTempInstance(pointTop);
401        Vector4.releaseTempInstance(pointFinal);
402        Vector4.releaseTempInstance(pointBottom);
403
404        final Vector3 oppositePoint = Vector3.fetchTempInstance();
405        final Vector3 adjacentPoint = Vector3.fetchTempInstance();
406        final Vector3 rootPoint = Vector3.fetchTempInstance();
407
408        int adj = 0, opp = 0;
409        int normalIndex = from * sizeX;
410        for (int row = from; row < to; row++) {
411            for (int col = 0; col < sizeX; col++) {
412                if (row == sizeY - 1) {
413                    if (col == sizeX - 1) { // last row, last col
414                        // up cross left
415                        adj = normalIndex - sizeX;
416                        opp = normalIndex - 1;
417                    } else { // last row, except for last col
418                        // right cross up
419                        adj = normalIndex + 1;
420                        opp = normalIndex - sizeX;
421                    }
422                } else {
423                    if (col == sizeX - 1) { // last column except for last row
424                        // left cross down
425                        adj = normalIndex - 1;
426                        opp = normalIndex + sizeX;
427                    } else { // most cases
428                        // down cross right
429                        adj = normalIndex + sizeX;
430                        opp = normalIndex + 1;
431                    }
432                }
433
434                final float x = vertBufArray[normalIndex * 3];
435                final float y = vertBufArray[normalIndex * 3 + 1];
436                final float z = vertBufArray[normalIndex * 3 + 2];
437
438                texBufArray[normalIndex * 2] = x * textureScale;
439                texBufArray[normalIndex * 2 + 1] = z * textureScale;
440
441                rootPoint.set(x, y, z);
442                adjacentPoint.set(vertBufArray[adj * 3], vertBufArray[adj * 3 + 1], vertBufArray[adj * 3 + 2]);
443                adjacentPoint.subtractLocal(rootPoint);
444                oppositePoint.set(vertBufArray[opp * 3], vertBufArray[opp * 3 + 1], vertBufArray[opp * 3 + 2]);
445                oppositePoint.subtractLocal(rootPoint);
446
447                adjacentPoint.crossLocal(oppositePoint).normalizeLocal();
448
449                normBufArray[normalIndex * 3] = adjacentPoint.getXf();
450                normBufArray[normalIndex * 3 + 1] = adjacentPoint.getYf();
451                normBufArray[normalIndex * 3 + 2] = adjacentPoint.getZf();
452
453                normalIndex++;
454            }
455        }
456
457        Vector3.releaseTempInstance(oppositePoint);
458        Vector3.releaseTempInstance(adjacentPoint);
459        Vector3.releaseTempInstance(rootPoint);
460    }
461
462    private void getWorldIntersectionHomogenous(final double planeHeight, final ReadOnlyVector2 screenPosition,
463            final ReadOnlyMatrix4 modelViewProjectionInverseMatrix, final Vector4 store) {
464        calculateIntersection(planeHeight, screenPosition, modelViewProjectionInverseMatrix);
465        store.set(origin);
466    }
467
468    private void getWorldIntersection(final double planeHeight, final ReadOnlyVector2 screenPosition,
469            final ReadOnlyMatrix4 modelViewProjectionInverseMatrix, final Vector3 store) {
470        calculateIntersection(planeHeight, screenPosition, modelViewProjectionInverseMatrix);
471        store.set(origin.getX(), origin.getY(), origin.getZ()).divideLocal(origin.getW());
472    }
473
474    private void calculateIntersection(final double planeHeight, final ReadOnlyVector2 screenPosition,
475            final ReadOnlyMatrix4 modelViewProjectionInverseMatrix) {
476        origin.set(screenPosition.getX() * 2 - 1, screenPosition.getY() * 2 - 1, -1, 1);
477        direction.set(screenPosition.getX() * 2 - 1, screenPosition.getY() * 2 - 1, 1, 1);
478
479        modelViewProjectionInverseMatrix.applyPre(origin, origin);
480        modelViewProjectionInverseMatrix.applyPre(direction, direction);
481
482        direction.subtractLocal(origin);
483
484        // final double t = (planeHeight * origin.getW() - origin.getY())
485        // / (direction.getY() - planeHeight * direction.getW());
486
487        if (Math.abs(direction.getY()) > MathUtils.EPSILON) {
488            final double t = (planeHeight - origin.getY()) / direction.getY();
489            direction.multiplyLocal(t);
490        } else {
491            direction.normalizeLocal();
492            direction.multiplyLocal(mainCamera.getFrustumFar());
493        }
494
495        origin.addLocal(direction);
496    }
497
498    /**
499     * <code>getSurfaceNormal</code> returns the normal of an arbitrary point on the terrain. The normal is linearly
500     * interpreted from the normals of the 4 nearest defined points. If the point provided is not within the bounds of
501     * the height map, null is returned.
502     *
503     * @param position
504     *            the vector representing the location to find a normal at.
505     * @param store
506     *            the Vector3 object to store the result in. If null, a new one is created.
507     * @return the normal vector at the provided location.
508     */
509    public Vector3 getSurfaceNormal(final Vector2 position, final Vector3 store) {
510        return getSurfaceNormal(position.getX(), position.getY(), store);
511    }
512
513    /**
514     * <code>getSurfaceNormal</code> returns the normal of an arbitrary point on the terrain. The normal is linearly
515     * interpreted from the normals of the 4 nearest defined points. If the point provided is not within the bounds of
516     * the height map, null is returned.
517     *
518     * @param position
519     *            the vector representing the location to find a normal at. Only the x and z values are used.
520     * @param store
521     *            the Vector3 object to store the result in. If null, a new one is created.
522     * @return the normal vector at the provided location.
523     */
524    public Vector3 getSurfaceNormal(final Vector3 position, final Vector3 store) {
525        return getSurfaceNormal(position.getX(), position.getZ(), store);
526    }
527
528    /**
529     * <code>getSurfaceNormal</code> returns the normal of an arbitrary point on the terrain. The normal is linearly
530     * interpreted from the normals of the 4 nearest defined points. If the point provided is not within the bounds of
531     * the height map, null is returned.
532     *
533     * @param x
534     *            the x coordinate to check.
535     * @param z
536     *            the z coordinate to check.
537     * @param store
538     *            the Vector3 object to store the result in. If null, a new one is created.
539     * @return the normal unit vector at the provided location.
540     */
541    public Vector3 getSurfaceNormal(final double x, final double z, Vector3 store) {
542        final double col = MathUtils.floor(x);
543        final double row = MathUtils.floor(z);
544
545        if (col < 0 || row < 0 || col >= sizeX - 1 || row >= sizeY - 1) {
546            return null;
547        }
548        final double intOnX = x - col, intOnZ = z - row;
549
550        if (store == null) {
551            store = new Vector3();
552        }
553
554        final Vector3 topLeft = store, topRight = new Vector3(), bottomLeft = new Vector3(),
555                bottomRight = new Vector3();
556
557        final int focalSpot = (int) (col + row * sizeX);
558
559        // find the heightmap point closest to this position (but will always
560        // be to the left ( < x) and above (< z) of the spot.
561        BufferUtils.populateFromBuffer(topLeft, normBuf, focalSpot);
562
563        // now find the next point to the right of topLeft's position...
564        BufferUtils.populateFromBuffer(topRight, normBuf, focalSpot + 1);
565
566        // now find the next point below topLeft's position...
567        BufferUtils.populateFromBuffer(bottomLeft, normBuf, focalSpot + sizeX);
568
569        // now find the next point below and to the right of topLeft's
570        // position...
571        BufferUtils.populateFromBuffer(bottomRight, normBuf, focalSpot + sizeX + 1);
572
573        // Use linear interpolation to find the height.
574        topLeft.lerpLocal(topRight, intOnX);
575        bottomLeft.lerpLocal(bottomRight, intOnX);
576        topLeft.lerpLocal(bottomLeft, intOnZ);
577        return topLeft.normalizeLocal();
578    }
579
580    /**
581     * <code>buildVertices</code> sets up the vertex and index arrays of the TriMesh.
582     *
583     * @param vertexCount
584     *            the vertex count
585     */
586    private void buildVertices(final int vertexCount) {
587        vertBuf = BufferUtils.createVector3Buffer(vertBuf, vertexCount);
588        _meshData.setVertexBuffer(vertBuf);
589
590        final Vector3 point = new Vector3();
591        for (int x = 0; x < sizeX; x++) {
592            for (int y = 0; y < sizeY; y++) {
593                point.set(x, 0, y);
594                BufferUtils.setInBuffer(point, vertBuf, (x + (y * sizeX)));
595            }
596        }
597
598        // set up the indices
599        final int triangleQuantity = ((sizeX - 1) * (sizeY - 1)) * 2;
600        final IndexBufferData<?> indices = BufferUtils.createIndexBufferData(triangleQuantity * 3, vertexCount - 1);
601        _meshData.setIndices(indices);
602
603        // go through entire array up to the second to last column.
604        for (int i = 0; i < (sizeX * (sizeY - 1)); i++) {
605            // we want to skip the top row.
606            if (i % ((sizeX * (i / sizeX + 1)) - 1) == 0 && i != 0) {
607                // logger.info("skip row: "+i+" cause: "+((sizeY * (i / sizeX + 1)) - 1));
608                continue;
609            } else {
610                // logger.info("i: "+i);
611            }
612            // set the top left corner.
613            indices.put(i);
614            // set the bottom right corner.
615            indices.put((1 + sizeX) + i);
616            // set the top right corner.
617            indices.put(1 + i);
618            // set the top left corner
619            indices.put(i);
620            // set the bottom left corner
621            indices.put(sizeX + i);
622            // set the bottom right corner
623            indices.put((1 + sizeX) + i);
624        }
625    }
626
627    static class DeamonThreadFactory implements ThreadFactory {
628        static final AtomicInteger poolNumber = new AtomicInteger(1);
629        final ThreadGroup group;
630        final AtomicInteger threadNumber = new AtomicInteger(1);
631        final String namePrefix;
632
633        DeamonThreadFactory() {
634            group = Thread.currentThread().getThreadGroup();
635            namePrefix = "ProjectedGrid Pool-" + poolNumber.getAndIncrement() + "-thread-";
636        }
637
638        @Override
639        public Thread newThread(final Runnable r) {
640            final Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
641            if (!t.isDaemon()) {
642                t.setDaemon(true);
643            }
644            if (t.getPriority() != Thread.NORM_PRIORITY) {
645                t.setPriority(Thread.NORM_PRIORITY);
646            }
647            return t;
648        }
649    }
650
651    public double getProjectorMinHeight() {
652        return projectorMinHeight;
653    }
654
655    public void setProjectorMinHeight(final double projectorMinHeight) {
656        this.projectorMinHeight = projectorMinHeight;
657    }
658
659    public boolean isDrawDebug() {
660        return drawDebug;
661    }
662
663    public void setDrawDebug(final boolean drawDebug) {
664        this.drawDebug = drawDebug;
665    }
666}