001/**
002 * Copyright (c) 2008-2014 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.scenegraph.shape;
012
013import java.nio.FloatBuffer;
014
015import com.ardor3d.math.MathUtils;
016import com.ardor3d.math.Vector3;
017import com.ardor3d.scenegraph.FloatBufferData;
018import com.ardor3d.scenegraph.IndexBufferData;
019import com.ardor3d.scenegraph.Mesh;
020import com.ardor3d.util.geom.BufferUtils;
021
022/**
023 * GeoSphere - generate a polygon mesh approximating a sphere by recursive subdivision. First approximation is an
024 * octahedron; each level of refinement increases the number of polygons by a factor of 4.
025 *
026 * Shared vertices are not retained, so numerical errors may produce cracks between polygons at high subdivision levels.
027 *
028 * Initial idea and text from C-Sourcecode by Jon Leech 3/24/89
029 */
030
031public class GeoSphere extends Mesh {
032
033    public enum TextureMode {
034        Original, Projected;
035    }
036
037    private int _maxlevels;
038    private boolean _usingIcosahedron = true;
039    private TextureMode _textureMode = TextureMode.Original;
040    private double _radius;
041
042    /**
043     * @param name
044     *            name of the spatial
045     * @param useIcosahedron
046     *            true to start with a 20 triangle mesh, false to start with a 8 triangle mesh
047     * @param radius
048     *            the radius of this sphere
049     * @param maxlevels
050     *            an integer &gt;= 1 setting the recursion level
051     * @param textureMode
052     *            the texture mode to use when generating texture coordinates
053     */
054    public GeoSphere(final String name, final boolean useIcosahedron, final double radius, final int maxlevels,
055            final TextureMode textureMode) {
056        super(name);
057        _maxlevels = maxlevels;
058        _radius = radius;
059        _maxlevels = maxlevels;
060        _usingIcosahedron = useIcosahedron;
061        _textureMode = textureMode;
062        updateGeometry();
063    }
064
065    /**
066     * Default Constructor for Savable use.
067     */
068    public GeoSphere() {
069    }
070
071    public double getRadius() {
072        return _radius;
073    }
074
075    public boolean isUsingIcosahedron() {
076        return _usingIcosahedron;
077    }
078
079    public void setTextureMode(final TextureMode textureMode) {
080        if (textureMode != _textureMode) {
081            _textureMode = textureMode;
082            updateGeometry();
083        }
084    }
085
086    public TextureMode getTextureMode() {
087        return _textureMode;
088    }
089
090    private void updateGeometry() {
091        final int initialTriangleCount = _usingIcosahedron ? 20 : 8;
092        final int initialVertexCount = _usingIcosahedron ? 12 : 6;
093        // number of triangles = initialTriangleCount * 4^(maxlevels-1)
094        final int tris = initialTriangleCount << ((_maxlevels - 1) * 2);
095
096        // number of vertBuf = (initialVertexCount + initialTriangleCount*4 +
097        // initialTriangleCount*4*4 + ...)
098        // = initialTriangleCount*(((4^maxlevels)-1)/(4-1)-1) +
099        // initialVertexCount
100        final int verts = initialTriangleCount * (((1 << (_maxlevels * 2)) - 1) / (4 - 1) - 1) + initialVertexCount
101                + calculateBorderTriangles(_maxlevels);
102
103        FloatBuffer vertBuf = _meshData.getVertexBuffer();
104        _meshData.setVertexBuffer(vertBuf = BufferUtils.createVector3Buffer(vertBuf, verts));
105        _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(_meshData.getNormalBuffer(), verts));
106        final FloatBufferData textureCoords = _meshData.getTextureCoords(0);
107        _meshData.setTextureCoords(new FloatBufferData(
108                BufferUtils.createVector2Buffer(textureCoords != null ? textureCoords.getBuffer() : null, verts), 2),
109                0);
110
111        int pos = 0;
112
113        Triangle[] old;
114        if (_usingIcosahedron) {
115            final int[] indices = new int[] { pos + 0, pos + 1, pos + 2, pos + 0, pos + 2, pos + 3, pos + 0, pos + 3,
116                    pos + 4, pos + 0, pos + 4, pos + 5, pos + 0, pos + 5, pos + 1, pos + 1, pos + 10, pos + 6, pos + 2,
117                    pos + 6, pos + 7, pos + 3, pos + 7, pos + 8, pos + 4, pos + 8, pos + 9, pos + 5, pos + 9, pos + 10,
118                    pos + 6, pos + 2, pos + 1, pos + 7, pos + 3, pos + 2, pos + 8, pos + 4, pos + 3, pos + 9, pos + 5,
119                    pos + 4, pos + 10, pos + 1, pos + 5, pos + 11, pos + 7, pos + 6, pos + 11, pos + 8, pos + 7,
120                    pos + 11, pos + 9, pos + 8, pos + 11, pos + 10, pos + 9, pos + 11, pos + 6, pos + 10 };
121            final double y = 0.4472 * _radius;
122            final double a = 0.8944 * _radius;
123            final double b = 0.2764 * _radius;
124            final double c = 0.7236 * _radius;
125            final double d = 0.8507 * _radius;
126            final double e = 0.5257 * _radius;
127            pos++;
128            put(new Vector3(0, _radius, 0));
129            pos++;
130            put(new Vector3(a, y, 0));
131            pos++;
132            put(new Vector3(b, y, -d));
133            pos++;
134            put(new Vector3(-c, y, -e));
135            pos++;
136            put(new Vector3(-c, y, e));
137            pos++;
138            put(new Vector3(b, y, d));
139            pos++;
140            put(new Vector3(c, -y, -e));
141            pos++;
142            put(new Vector3(-b, -y, -d));
143            pos++;
144            put(new Vector3(-a, -y, 0));
145            pos++;
146            put(new Vector3(-b, -y, d));
147            pos++;
148            put(new Vector3(c, -y, e));
149            pos++;
150            put(new Vector3(0, -_radius, 0));
151            final Triangle[] ikosaedron = new Triangle[indices.length / 3];
152            for (int i = 0; i < ikosaedron.length; i++) {
153                final Triangle triangle = ikosaedron[i] = new Triangle();
154                triangle.pt[0] = indices[i * 3];
155                triangle.pt[1] = indices[i * 3 + 1];
156                triangle.pt[2] = indices[i * 3 + 2];
157            }
158
159            old = ikosaedron;
160        } else {
161            /* Six equidistant points lying on the unit sphere */
162            final Vector3 XPLUS = new Vector3(_radius, 0, 0); /* X */
163            final Vector3 XMIN = new Vector3(-_radius, 0, 0); /* -X */
164            final Vector3 YPLUS = new Vector3(0, _radius, 0); /* Y */
165            final Vector3 YMIN = new Vector3(0, -_radius, 0); /* -Y */
166            final Vector3 ZPLUS = new Vector3(0, 0, _radius); /* Z */
167            final Vector3 ZMIN = new Vector3(0, 0, -_radius); /* -Z */
168
169            final int xplus = pos++;
170            put(XPLUS);
171            final int xmin = pos++;
172            put(XMIN);
173            final int yplus = pos++;
174            put(YPLUS);
175            final int ymin = pos++;
176            put(YMIN);
177            final int zplus = pos++;
178            put(ZPLUS);
179            final int zmin = pos++;
180            put(ZMIN);
181
182            final Triangle[] octahedron = new Triangle[] { new Triangle(yplus, zplus, xplus),
183                    new Triangle(xmin, zplus, yplus), new Triangle(ymin, zplus, xmin), new Triangle(xplus, zplus, ymin),
184                    new Triangle(zmin, yplus, xplus), new Triangle(zmin, xmin, yplus), new Triangle(zmin, ymin, xmin),
185                    new Triangle(zmin, xplus, ymin) };
186
187            old = octahedron;
188        }
189
190        final Vector3 pt0 = new Vector3();
191        final Vector3 pt1 = new Vector3();
192        final Vector3 pt2 = new Vector3();
193
194        /* Subdivide each starting triangle (maxlevels - 1) times */
195        for (int level = 1; level < _maxlevels; level++) {
196            /* Allocate a next triangle[] */
197            final Triangle[] next = new Triangle[old.length * 4];
198            for (int i = 0; i < next.length; i++) {
199                next[i] = new Triangle();
200            }
201
202            /**
203             * Subdivide each polygon in the old approximation and normalize the next points thus generated to lie on
204             * the surface of the unit sphere. Each input triangle with vertBuf labeled [0,1,2] as shown below will be
205             * turned into four next triangles:
206             *
207             * <pre>
208             * Make next points
209             *   a = (0+2)/2
210             *   b = (0+1)/2
211             *   c = (1+2)/2
212             *
213             * 1   /\   Normalize a, b, c
214             *    /  \
215             * b /____\ c
216             *
217             * Construct next triangles
218             *
219             *    /\    /\   [0,b,a]
220             *   /  \  /  \  [b,1,c]
221             *  /____\/____\ [a,b,c]
222             *  0 a 2 [a,c,2]
223             * </pre>
224             */
225            for (int i = 0; i < old.length; i++) {
226                int newi = i * 4;
227                final Triangle oldt = old[i];
228                Triangle newt = next[newi];
229
230                BufferUtils.populateFromBuffer(pt0, vertBuf, oldt.pt[0]);
231                BufferUtils.populateFromBuffer(pt1, vertBuf, oldt.pt[1]);
232                BufferUtils.populateFromBuffer(pt2, vertBuf, oldt.pt[2]);
233                final Vector3 av = createMidpoint(pt0, pt2).normalizeLocal().multiplyLocal(_radius);
234                final Vector3 bv = createMidpoint(pt0, pt1).normalizeLocal().multiplyLocal(_radius);
235                final Vector3 cv = createMidpoint(pt1, pt2).normalizeLocal().multiplyLocal(_radius);
236                final int a = pos++;
237                put(av);
238                final int b = pos++;
239                put(bv);
240                final int c = pos++;
241                put(cv);
242
243                newt.pt[0] = oldt.pt[0];
244                newt.pt[1] = b;
245                newt.pt[2] = a;
246                newt = next[++newi];
247
248                newt.pt[0] = b;
249                newt.pt[1] = oldt.pt[1];
250                newt.pt[2] = c;
251                newt = next[++newi];
252
253                newt.pt[0] = a;
254                newt.pt[1] = b;
255                newt.pt[2] = c;
256                newt = next[++newi];
257
258                newt.pt[0] = a;
259                newt.pt[1] = c;
260                newt.pt[2] = oldt.pt[2];
261            }
262
263            /* Continue subdividing next triangles */
264            old = next;
265        }
266
267        final IndexBufferData<?> indexBuffer = BufferUtils.createIndexBufferData(tris * 3, verts - 1);
268        _meshData.setIndices(indexBuffer);
269
270        int carryIntIndex = _meshData.getVertexBuffer().position() / 3;
271        for (final Triangle triangle : old) {
272            for (final int aPt : triangle.pt) {
273                final Vector3 point = new Vector3();
274                BufferUtils.populateFromBuffer(point, _meshData.getVertexBuffer(), aPt);
275                if (point.getX() > 0 && point.getY() == 0) {
276                    // Find out which 'y' side the triangle is on
277                    final double yCenter = (_meshData.getVertexBuffer().get(triangle.pt[0] * 3 + 1)
278                            + _meshData.getVertexBuffer().get(triangle.pt[1] * 3 + 1)
279                            + _meshData.getVertexBuffer().get(triangle.pt[2] * 3 + 1)) / 3.0;
280                    if (yCenter > 0.0) {
281                        put(point, true);
282                        indexBuffer.put(carryIntIndex++);
283                        continue;
284                    }
285                }
286                indexBuffer.put(aPt);
287            }
288        }
289    }
290
291    private void put(final Vector3 vec) {
292        put(vec, false);
293    }
294
295    private void put(final Vector3 vec, final boolean begining) {
296        final FloatBuffer vertBuf = _meshData.getVertexBuffer();
297        vertBuf.put((float) vec.getX());
298        vertBuf.put((float) vec.getY());
299        vertBuf.put((float) vec.getZ());
300
301        final double length = vec.length();
302        final FloatBuffer normBuf = _meshData.getNormalBuffer();
303        final double xNorm = vec.getX() / length;
304        normBuf.put((float) xNorm);
305        final double yNorm = vec.getY() / length;
306        normBuf.put((float) yNorm);
307        final double zNorm = vec.getZ() / length;
308        normBuf.put((float) zNorm);
309
310        final FloatBuffer texBuf = _meshData.getTextureCoords(0).getBuffer();
311        if (vec.getX() > 0.0 && vec.getY() == 0.0) {
312            if (begining) {
313                texBuf.put(0);
314            } else {
315                texBuf.put(1);
316            }
317        } else {
318            texBuf.put((float) ((Math.atan2(yNorm, xNorm) / (2 * Math.PI) + 1) % 1));
319        }
320
321        double vPos = 0;
322        switch (_textureMode) {
323            case Original:
324                vPos = .5 * (zNorm + 1);
325                break;
326            case Projected:
327                vPos = MathUtils.INV_PI * (MathUtils.HALF_PI + Math.asin(zNorm));
328                break;
329        }
330        texBuf.put((float) vPos);
331    }
332
333    private int calculateBorderTriangles(int levels) {
334        int current = 108;
335        // Pattern starts at 4
336        levels -= 4;
337        while (levels-- > 0) {
338            current = 2 * current + 12;
339        }
340        return current;
341    }
342
343    /**
344     * Compute the average of two vectors.
345     *
346     * @param a
347     *            first vector
348     * @param b
349     *            second vector
350     * @return the average of two points
351     */
352    protected Vector3 createMidpoint(final Vector3 a, final Vector3 b) {
353        return new Vector3((a.getX() + b.getX()) * 0.5, (a.getY() + b.getY()) * 0.5, (a.getZ() + b.getZ()) * 0.5);
354    }
355
356    static class Triangle {
357        int[] pt = new int[3]; /* Vertices of triangle */
358
359        public Triangle() {
360        }
361
362        public Triangle(final int pt0, final int pt1, final int pt2) {
363            pt[0] = pt0;
364            pt[1] = pt1;
365            pt[2] = pt2;
366        }
367    }
368}