1 // Copyright © 2008 JMonkeyEngine, all rights reserved.
2 // See the accompanying LICENSE file for terms and conditions of use.
4 package com.jme.scene.shape;
6 import java.nio.FloatBuffer;
7 import java.nio.IntBuffer;
9 import com.jme.math.FastMath;
10 import com.jme.math.Vector3f;
11 import com.jme.scene.TexCoords;
12 import com.jme.scene.TriMesh;
13 import com.jme.util.geom.BufferUtils;
16 * A polygon mesh approximating a sphere by recursive subdivision.
18 * First approximation is an octahedron; each level of refinement increases the
19 * number of polygons by a factor of 4.
21 * Shared vertices are not retained, so numerical errors may produce cracks
22 * between polygons at high subdivision levels.
24 * TODO: texture co-ordinates could be nicer
26 * @author John Leech - initial idea and original C implementation
27 * @author Irrisor - Java port and JME optimisation
28 * @version $Revision$, $Date$
30 public class GeoSphere extends TriMesh {
32 private static final long serialVersionUID = 1L;
34 private int numLevels;
36 private boolean usingIcosahedron = true;
38 /** <strong>NOT API:</strong> for internal use, do not call from user code. */
42 * @param name the name of the spatial
44 * true to start with an 20 triangles, false to start with 8
47 * an integer >= 1 setting the recursion level
48 * @see jmetest.shape.TestGeoSphere
50 public GeoSphere(String name, boolean ikosa, int maxlevels) {
52 updateGeometry(maxlevels, ikosa);
56 * Compute the average of two vectors.
62 * @return the average of two points
64 private Vector3f createMidpoint(Vector3f a, Vector3f b) {
65 return new Vector3f((a.x + b.x) * 0.5f, (a.y + b.y) * 0.5f,
69 public int getNumLevels() {
74 * TODO: radius is always 1
78 public float getRadius() {
82 public boolean isUsingIcosahedron() {
83 return usingIcosahedron;
86 private void put(Vector3f vec) {
87 FloatBuffer vertBuf = getVertexBuffer();
92 float length = vec.length();
93 FloatBuffer normBuf = getNormalBuffer();
94 float xNorm = vec.x / length;
96 float yNorm = vec.y / length;
98 float zNorm = vec.z / length;
101 FloatBuffer texBuf = getTextureCoords(0).coords;
102 texBuf.put((FastMath.atan2(yNorm, xNorm) / (2 * FastMath.PI) + 1) % 1);
103 texBuf.put(zNorm / 2 + 0.5f);
106 private void updateGeometry(int maxLevels, boolean icosahedron) {
107 this.numLevels = maxLevels;
108 this.usingIcosahedron = icosahedron;
109 int initialTriangleCount = icosahedron ? 20 : 8;
110 int initialVertexCount = icosahedron ? 12 : 6;
111 // number of triangles = initialTriangleCount * 4^(maxlevels-1)
112 int triangleQuantity = initialTriangleCount << ((numLevels - 1) * 2);
113 setTriangleQuantity(triangleQuantity);
114 // number of vertBuf = (initialVertexCount + initialTriangleCount*4 +
115 // initialTriangleCount*4*4 + ...)
116 // = initialTriangleCount*(((4^maxlevels)-1)/(4-1)-1) +
117 // initialVertexCount
118 int vertQuantity = initialTriangleCount
119 * (((1 << (numLevels * 2)) - 1) / (4 - 1) - 1)
120 + initialVertexCount;
121 setVertexCount(vertQuantity);
123 FloatBuffer vertBuf = getVertexBuffer();
124 setVertexBuffer(vertBuf = BufferUtils.createVector3Buffer(vertBuf,
126 setNormalBuffer(BufferUtils.createVector3Buffer(getNormalBuffer(),
128 TexCoords textureCoords = getTextureCoords(0);
129 setTextureCoords(new TexCoords(BufferUtils.createVector3Buffer(textureCoords != null ? textureCoords.coords : null,
136 int[] indices = new int[] {
137 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 5, 1, 1, 10, 6, 2, 6,
138 7, 3, 7, 8, 4, 8, 9, 5, 9, 10, 6, 2, 1, 7, 3, 2, 8, 4, 3, 9,
139 5, 4, 10, 1, 5, 11, 7, 6, 11, 8, 7, 11, 9, 8, 11, 10, 9, 11,
148 put(new Vector3f(0, 1, 0));
149 put(new Vector3f(a, y, 0));
150 put(new Vector3f(b, y, -d));
151 put(new Vector3f(-c, y, -e));
152 put(new Vector3f(-c, y, e));
153 put(new Vector3f(b, y, d));
154 put(new Vector3f(c, -y, -e));
155 put(new Vector3f(-b, -y, -d));
156 put(new Vector3f(-a, -y, 0));
157 put(new Vector3f(-b, -y, d));
158 put(new Vector3f(c, -y, e));
159 put(new Vector3f(0, -1, 0));
162 Triangle[] ikosaedron = new Triangle[indices.length / 3];
163 for (int i = 0; i < ikosaedron.length; i++) {
164 Triangle triangle = ikosaedron[i] = new Triangle();
165 triangle.pt[0] = indices[i * 3];
166 triangle.pt[1] = indices[i * 3 + 1];
167 triangle.pt[2] = indices[i * 3 + 2];
172 /* Six equidistant points lying on the unit sphere */
173 final Vector3f XPLUS = new Vector3f(1, 0, 0); /* X */
174 final Vector3f XMIN = new Vector3f(-1, 0, 0); /* -X */
175 final Vector3f YPLUS = new Vector3f(0, 1, 0); /* Y */
176 final Vector3f YMIN = new Vector3f(0, -1, 0); /* -Y */
177 final Vector3f ZPLUS = new Vector3f(0, 0, 1); /* Z */
178 final Vector3f ZMIN = new Vector3f(0, 0, -1); /* -Z */
193 Triangle[] octahedron = new Triangle[] {
194 new Triangle(yplus, zplus, xplus),
195 new Triangle(xmin, zplus, yplus),
196 new Triangle(ymin, zplus, xmin),
197 new Triangle(xplus, zplus, ymin),
198 new Triangle(zmin, yplus, xplus),
199 new Triangle(zmin, xmin, yplus),
200 new Triangle(zmin, ymin, xmin),
201 new Triangle(zmin, xplus, ymin) };
206 Vector3f pt0 = new Vector3f();
207 Vector3f pt1 = new Vector3f();
208 Vector3f pt2 = new Vector3f();
210 /* Subdivide each starting triangle (maxlevels - 1) times */
211 for (int level = 1; level < numLevels; level++) {
212 /* Allocate a next triangle[] */
213 Triangle[] next = new Triangle[old.length * 4];
214 for (int i = 0; i < next.length; i++) {
215 next[i] = new Triangle();
219 * Subdivide each polygon in the old approximation and normalize the
220 * next points thus generated to lie on the surface of the unit
221 * sphere. Each input triangle with vertBuf labelled [0,1,2] as
222 * shown below will be turned into four next triangles:
229 * 1 /\ Normalize a, b, c
233 * Construct next triangles
237 * /____\/____\ [a,b,c]
240 for (int i = 0; i < old.length; i++) {
242 Triangle oldt = old[i], newt = next[newi];
244 BufferUtils.populateFromBuffer(pt0, vertBuf, oldt.pt[0]);
245 BufferUtils.populateFromBuffer(pt1, vertBuf, oldt.pt[1]);
246 BufferUtils.populateFromBuffer(pt2, vertBuf, oldt.pt[2]);
247 Vector3f av = createMidpoint(pt0, pt2).normalizeLocal();
248 Vector3f bv = createMidpoint(pt0, pt1).normalizeLocal();
249 Vector3f cv = createMidpoint(pt1, pt2).normalizeLocal();
257 newt.pt[0] = oldt.pt[0];
263 newt.pt[1] = oldt.pt[1];
274 newt.pt[2] = oldt.pt[2];
277 /* Continue subdividing next triangles */
281 IntBuffer indexBuffer = BufferUtils
282 .createIntBuffer(triangleQuantity * 3);
283 setIndexBuffer(indexBuffer);
285 for (Triangle triangle : old) {
286 for (int aPt : triangle.pt) {
287 indexBuffer.put(aPt);
292 static class Triangle {
293 int[] pt = new int[3]; /* Vertices of triangle */
297 public Triangle(int pt0, int pt1, int pt2) {