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 >= 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}