Revision ebe0476f
Added by Leszek Koltunski almost 7 years ago
| src/main/java/org/distorted/library/mesh/MeshSphere.java | ||
|---|---|---|
| 23 | 23 |
/** |
| 24 | 24 |
* Create a Mesh which approximates a sphere. |
| 25 | 25 |
* <p> |
| 26 |
* Do so by dividing each of the 20 faces of the regular icosahedron into smaller triangles and inflating |
|
| 27 |
* those to lay on the surface of the sphere. |
|
| 26 |
* Do so by starting off with a 16-faced solid which is basically a regular dodecahedron with each |
|
| 27 |
* of its 8 faces vertically split into 2 triangles, and which each step divide each of its triangular |
|
| 28 |
* faces into smaller and smaller subtriangles and inflate their vertices to lay on the surface or the |
|
| 29 |
* sphere. |
|
| 28 | 30 |
*/ |
| 29 | 31 |
public class MeshSphere extends MeshBase |
| 30 | 32 |
{
|
| 31 |
private static final int NUMFACES = 20;
|
|
| 33 |
private static final int NUMFACES = 16;
|
|
| 32 | 34 |
private static final double sqrt2 = Math.sqrt(2.0); |
| 33 | 35 |
private static final double P = Math.PI; |
| 34 |
private static final double A = 0.463647609; // arctan(0.5), +-latitude of the 10 'middle' vertices |
|
| 35 |
// https://en.wikipedia.org/wiki/Regular_icosahedron |
|
| 36 | 36 |
|
| 37 |
// An array of 20 entries, each describing a single face of the regular icosahedron in an (admittedly) |
|
| 38 |
// weird fashion. |
|
| 39 |
// Each face of a regular icosahedron is a equilateral triangle, with 2 vertices on the same latitude. |
|
| 37 |
// An array of 16 entries, each describing a single face of the solid in an (admittedly) weird |
|
| 38 |
// fashion. Each face is a triangle, with 2 vertices on the same latitude. |
|
| 40 | 39 |
// Single row is (longitude of V1, longitude of V2, (common) latitude of V1 and V2, latitude of V3) |
| 41 | 40 |
// longitude of V3 is simply midpoint of V1 and V2 so we don't have to specify it here. |
| 42 | 41 |
|
| 43 | 42 |
private static final double FACES[][] = {
|
| 44 |
{ 0.0*P, 0.4*P, A, 0.5*P },
|
|
| 45 |
{ 0.4*P, 0.8*P, A, 0.5*P },
|
|
| 46 |
{ 0.8*P, 1.2*P, A, 0.5*P }, // 5 'top' faces with
|
|
| 47 |
{ 1.2*P, 1.6*P, A, 0.5*P }, // the North Pole
|
|
| 48 |
{ 1.6*P, 2.0*P, A, 0.5*P },
|
|
| 49 |
|
|
| 50 |
{ 0.0*P, 0.4*P, A, -A },
|
|
| 51 |
{ 0.4*P, 0.8*P, A, -A },
|
|
| 52 |
{ 0.8*P, 1.2*P, A, -A }, // 5 faces mostly above
|
|
| 53 |
{ 1.2*P, 1.6*P, A, -A }, // the equator
|
|
| 54 |
{ 1.6*P, 2.0*P, A, -A },
|
|
| 55 |
|
|
| 56 |
{ 0.2*P, 0.6*P, -A, A },
|
|
| 57 |
{ 0.6*P, 1.0*P, -A, A },
|
|
| 58 |
{ 1.0*P, 1.4*P, -A, A }, // 5 faces mostly below
|
|
| 59 |
{ 1.4*P, 1.8*P, -A, A }, // the equator
|
|
| 60 |
{ 1.8*P, 0.2*P, -A, A },
|
|
| 61 |
|
|
| 62 |
{ 0.2*P, 0.6*P, -A,-0.5*P },
|
|
| 63 |
{ 0.6*P, 1.0*P, -A,-0.5*P },
|
|
| 64 |
{ 1.0*P, 1.4*P, -A,-0.5*P }, // 5 'bottom' faces with
|
|
| 65 |
{ 1.4*P, 1.8*P, -A,-0.5*P }, // the South Pole
|
|
| 66 |
{ 1.8*P, 0.2*P, -A,-0.5*P },
|
|
| 43 |
|
|
| 44 |
{ 0.00*P, 0.25*P, 0.0, 0.5*P },
|
|
| 45 |
{ 0.25*P, 0.50*P, 0.0, 0.5*P },
|
|
| 46 |
{ 0.50*P, 0.75*P, 0.0, 0.5*P },
|
|
| 47 |
{ 0.75*P, 1.00*P, 0.0, 0.5*P },
|
|
| 48 |
{ 1.00*P, 1.25*P, 0.0, 0.5*P },
|
|
| 49 |
{ 1.25*P, 1.50*P, 0.0, 0.5*P },
|
|
| 50 |
{ 1.50*P, 1.75*P, 0.0, 0.5*P },
|
|
| 51 |
{ 1.75*P, 0.00*P, 0.0, 0.5*P },
|
|
| 52 |
|
|
| 53 |
{ 0.00*P, 0.25*P, 0.0,-0.5*P },
|
|
| 54 |
{ 0.25*P, 0.50*P, 0.0,-0.5*P },
|
|
| 55 |
{ 0.50*P, 0.75*P, 0.0,-0.5*P },
|
|
| 56 |
{ 0.75*P, 1.00*P, 0.0,-0.5*P },
|
|
| 57 |
{ 1.00*P, 1.25*P, 0.0,-0.5*P },
|
|
| 58 |
{ 1.25*P, 1.50*P, 0.0,-0.5*P },
|
|
| 59 |
{ 1.50*P, 1.75*P, 0.0,-0.5*P },
|
|
| 60 |
{ 1.75*P, 0.00*P, 0.0,-0.5*P },
|
|
| 67 | 61 |
}; |
| 68 | 62 |
private int currentVert; |
| 69 | 63 |
private int numVertices; |
| 70 | 64 |
|
| 71 | 65 |
/////////////////////////////////////////////////////////////////////////////////////////////////// |
| 72 |
// Each of the 20 faces of the icosahedron requires (level*level + 4*level) vertices for the face
|
|
| 66 |
// Each of the 16 faces of the solid requires (level*level + 4*level) vertices for the face
|
|
| 73 | 67 |
// itself and a join to the next face (which requires 2 vertices). We don't need the join in case |
| 74 |
// of the last, 20th face, thus the -2.
|
|
| 68 |
// of the last, 16th face, thus the -2.
|
|
| 75 | 69 |
// (level*level +4*level) because there are level*level little triangles, each requiring new vertex, |
| 76 | 70 |
// plus 2 extra vertices to start off a row and 2 to move to the next row (or the next face in case |
| 77 | 71 |
// of the last row) and there are 'level' rows. |
| 78 |
// |
|
| 79 |
// Now to this we need to add 6*(level-1) vertices for the internal seams in the three triangles |
|
| 80 |
// in the back ( 2,7,16 in the list above ): 3 triangles, level-1 rows of more than 1 triangle in |
|
| 81 |
// each, 2 extra seam vertices in each row. |
|
| 82 | 72 |
|
| 83 | 73 |
private void computeNumberOfVertices(int level) |
| 84 | 74 |
{
|
| 85 |
numVertices = NUMFACES*level*(level+4) -2 + 6*(level-1);
|
|
| 75 |
numVertices = NUMFACES*level*(level+4) -2; |
|
| 86 | 76 |
currentVert = 0; |
| 87 | 77 |
} |
| 88 | 78 |
|
| 89 | 79 |
/////////////////////////////////////////////////////////////////////////////////////////////////// |
| 90 | 80 |
|
| 91 |
private int convertAng(double radian) |
|
| 92 |
{
|
|
| 93 |
return (int)(radian*180.0/P); |
|
| 94 |
} |
|
| 95 |
|
|
| 96 |
/////////////////////////////////////////////////////////////////////////////////////////////////// |
|
| 97 |
|
|
| 98 |
private void repeatVertex(float[] attribs, int diff) |
|
| 81 |
private void repeatVertex(float[] attribs) |
|
| 99 | 82 |
{
|
| 100 | 83 |
//android.util.Log.e("sphere", "repeat last!");
|
| 101 | 84 |
|
| 102 | 85 |
if( currentVert>0 ) |
| 103 | 86 |
{
|
| 104 |
attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB ] = attribs[VERT_ATTRIBS*(currentVert-diff) + POS_ATTRIB ];
|
|
| 105 |
attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB+1] = attribs[VERT_ATTRIBS*(currentVert-diff) + POS_ATTRIB+1];
|
|
| 106 |
attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB+2] = attribs[VERT_ATTRIBS*(currentVert-diff) + POS_ATTRIB+2];
|
|
| 87 |
attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB ] = attribs[VERT_ATTRIBS*(currentVert-1) + POS_ATTRIB ];
|
|
| 88 |
attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB+1] = attribs[VERT_ATTRIBS*(currentVert-1) + POS_ATTRIB+1];
|
|
| 89 |
attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB+2] = attribs[VERT_ATTRIBS*(currentVert-1) + POS_ATTRIB+2];
|
|
| 107 | 90 |
|
| 108 |
attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB ] = attribs[VERT_ATTRIBS*(currentVert-diff) + NOR_ATTRIB ];
|
|
| 109 |
attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB+1] = attribs[VERT_ATTRIBS*(currentVert-diff) + NOR_ATTRIB+1];
|
|
| 110 |
attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB+2] = attribs[VERT_ATTRIBS*(currentVert-diff) + NOR_ATTRIB+2];
|
|
| 91 |
attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB ] = attribs[VERT_ATTRIBS*(currentVert-1) + NOR_ATTRIB ];
|
|
| 92 |
attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB+1] = attribs[VERT_ATTRIBS*(currentVert-1) + NOR_ATTRIB+1];
|
|
| 93 |
attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB+2] = attribs[VERT_ATTRIBS*(currentVert-1) + NOR_ATTRIB+2];
|
|
| 111 | 94 |
|
| 112 |
attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB ] = attribs[VERT_ATTRIBS*(currentVert-diff) + INF_ATTRIB ];
|
|
| 113 |
attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB+1] = attribs[VERT_ATTRIBS*(currentVert-diff) + INF_ATTRIB+1];
|
|
| 114 |
attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB+2] = attribs[VERT_ATTRIBS*(currentVert-diff) + INF_ATTRIB+2];
|
|
| 95 |
attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB ] = attribs[VERT_ATTRIBS*(currentVert-1) + INF_ATTRIB ];
|
|
| 96 |
attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB+1] = attribs[VERT_ATTRIBS*(currentVert-1) + INF_ATTRIB+1];
|
|
| 97 |
attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB+2] = attribs[VERT_ATTRIBS*(currentVert-1) + INF_ATTRIB+2];
|
|
| 115 | 98 |
|
| 116 |
attribs[VERT_ATTRIBS*currentVert + TEX_ATTRIB ] = attribs[VERT_ATTRIBS*(currentVert-diff) + TEX_ATTRIB ];
|
|
| 117 |
attribs[VERT_ATTRIBS*currentVert + TEX_ATTRIB+1] = attribs[VERT_ATTRIBS*(currentVert-diff) + TEX_ATTRIB+1];
|
|
| 99 |
attribs[VERT_ATTRIBS*currentVert + TEX_ATTRIB ] = attribs[VERT_ATTRIBS*(currentVert-1) + TEX_ATTRIB ];
|
|
| 100 |
attribs[VERT_ATTRIBS*currentVert + TEX_ATTRIB+1] = attribs[VERT_ATTRIBS*(currentVert-1) + TEX_ATTRIB+1];
|
|
| 118 | 101 |
|
| 119 | 102 |
currentVert++; |
| 120 | 103 |
} |
| ... | ... | |
| 172 | 155 |
// (level-1, 0, level) -> (lonV2,latV12) |
| 173 | 156 |
|
| 174 | 157 |
private void addVertex(float[] attribs, int column, int row, int level, |
| 175 |
double lonV1, double lonV2, double latV12, double latV3, boolean lastInRow)
|
|
| 158 |
double lonV1, double lonV2, double latV12, double latV3) |
|
| 176 | 159 |
{
|
| 177 | 160 |
double quotX = (double)column/level; |
| 178 | 161 |
double quotY = (double)row /level; |
| ... | ... | |
| 227 | 210 |
|
| 228 | 211 |
//////////////////////////////////////////////////////////////////////////////////////////////// |
| 229 | 212 |
// Problem: on the 'change of date' line in the back of the sphere, some triangles see texX |
| 230 |
// coords suddenly jump from 1-epsilon to 0+epsilon, which looks like a seam with a narrow copy |
|
| 231 |
// of the whole texture there. |
|
| 232 |
// Solution: each such 'jump' triangle, if it is the last in a row, should have the texX of its |
|
| 233 |
// last (and maybe forelast as well) vertex remapped to 1.0. |
|
| 234 |
// If such triangle is not the last in its row, we need to add two extra vertices between it and |
|
| 235 |
// the next one, remap the old ones' texX to 1.0, and set one of the two new ones' texX to 0.0. |
|
| 213 |
// coords suddenly jump from 1-epsilon to 0, which looks like a seam with a narrow copy of |
|
| 214 |
// the whole texture there. Solution: remap texX to 1.0. |
|
| 236 | 215 |
//////////////////////////////////////////////////////////////////////////////////////////////// |
| 237 | 216 |
|
| 238 |
if( currentVert>=3 ) |
|
| 217 |
if( currentVert>=3 && texX==0.0 )
|
|
| 239 | 218 |
{
|
| 240 | 219 |
double tex1 = attribs[VERT_ATTRIBS*(currentVert-2) + TEX_ATTRIB]; |
| 241 | 220 |
double tex2 = attribs[VERT_ATTRIBS*(currentVert-3) + TEX_ATTRIB]; |
| 242 |
double y1 = attribs[VERT_ATTRIBS*(currentVert-2) + INF_ATTRIB + 1]; |
|
| 243 |
double y2 = attribs[VERT_ATTRIBS*(currentVert-3) + INF_ATTRIB + 1]; |
|
| 244 | 221 |
|
| 245 |
if( tex1!=tex2 || y1!=y2 ) // if the triangle is not degenerate |
|
| 222 |
// if the triangle is not degenerate and last vertex was on the western hemisphere |
|
| 223 |
if( tex1!=tex2 && tex1>0.5 ) |
|
| 246 | 224 |
{
|
| 247 |
double diff1 = Math.abs(texX-tex1); |
|
| 248 |
double diff2 = Math.abs(texX-tex2); |
|
| 249 |
|
|
| 250 |
if( diff1>0.5 || diff2>0.5 ) // a jump in texture coords which can only happen along the |
|
| 251 |
{ // 'date change' line. Have to correct!
|
|
| 252 |
if( lastInRow ) |
|
| 253 |
{
|
|
| 254 |
attribs[VERT_ATTRIBS*(currentVert-1) + TEX_ATTRIB] = 1.0f; |
|
| 255 |
if( tex1<tex2 ) attribs[VERT_ATTRIBS*(currentVert-2) + TEX_ATTRIB] = 1.0f; |
|
| 256 |
} |
|
| 257 |
else |
|
| 258 |
{
|
|
| 259 |
if( latitude> -A ) // one of the top two triangles being corrected |
|
| 260 |
{
|
|
| 261 |
if (2*column+row-1 == level) // last such triangle in a row; have to introduce extra 2 vertices. |
|
| 262 |
{
|
|
| 263 |
/* |
|
| 264 |
android.util.Log.e("sphere", "LAST max diff exceeded: " + convertAng(longitude) + " " + convertAng(latitude) + " texX=" + texX);
|
|
| 265 |
android.util.Log.d("sphere", " tex1=" + tex1 + " tex2=" + tex2 + " y1=" + y1 + " y2=" + y2);
|
|
| 266 |
android.util.Log.d("sphere", " latV12=" + latV12 + " latV3=" + latV3);
|
|
| 267 |
*/ |
|
| 268 |
repeatVertex(attribs, 2); |
|
| 269 |
repeatVertex(attribs, 2); |
|
| 270 |
|
|
| 271 |
attribs[VERT_ATTRIBS*(currentVert-2) + TEX_ATTRIB] = 0.0f; |
|
| 272 |
attribs[VERT_ATTRIBS*(currentVert-3) + TEX_ATTRIB] = 1.0f; |
|
| 273 |
|
|
| 274 |
if (tex1 < tex2) attribs[VERT_ATTRIBS*(currentVert-4) + TEX_ATTRIB] = 1.0f; |
|
| 275 |
} |
|
| 276 |
if (2*column+row == level) // not the last; just remap current vertex' texX to 1.0. |
|
| 277 |
{
|
|
| 278 |
/* |
|
| 279 |
android.util.Log.e("sphere", "NOT LAST max diff exceeded: " + convertAng(longitude) + " " + convertAng(latitude) + " texX=" + texX);
|
|
| 280 |
android.util.Log.d("sphere", " tex1=" + tex1 + " tex2=" + tex2 + " y1=" + y1 + " y2=" + y2);
|
|
| 281 |
android.util.Log.d("sphere", " latV12=" + latV12 + " latV3=" + latV3);
|
|
| 282 |
*/ |
|
| 283 |
attribs[VERT_ATTRIBS*(currentVert-1) + TEX_ATTRIB] = 1.0f; |
|
| 284 |
} |
|
| 285 |
} |
|
| 286 |
else // bottom triangle |
|
| 287 |
{
|
|
| 288 |
/* |
|
| 289 |
android.util.Log.e("sphere", "BOTTOM LAST max diff exceeded: " + convertAng(longitude) + " " + convertAng(latitude) + " texX=" + texX);
|
|
| 290 |
android.util.Log.d("sphere", " tex1=" + tex1 + " tex2=" + tex2 + " y1=" + y1 + " y2=" + y2);
|
|
| 291 |
android.util.Log.d("sphere", " latV12=" + latV12 + " latV3=" + latV3);
|
|
| 292 |
*/ |
|
| 293 |
repeatVertex(attribs, 2); |
|
| 294 |
repeatVertex(attribs, 2); |
|
| 295 |
|
|
| 296 |
attribs[VERT_ATTRIBS*(currentVert-3) + TEX_ATTRIB] = 1.0f; |
|
| 297 |
} |
|
| 298 |
} |
|
| 299 |
} |
|
| 225 |
attribs[VERT_ATTRIBS*(currentVert-1) + TEX_ATTRIB] = 1.0f; |
|
| 300 | 226 |
} |
| 301 | 227 |
} |
| 302 |
|
|
| 303 |
//////////////////////////////////////////////////////////////////////////////////////////////// |
|
| 304 |
// End problem |
|
| 305 |
//////////////////////////////////////////////////////////////////////////////////////////////// |
|
| 306 | 228 |
} |
| 307 | 229 |
|
| 308 | 230 |
/////////////////////////////////////////////////////////////////////////////////////////////////// |
| ... | ... | |
| 313 | 235 |
{
|
| 314 | 236 |
for (int column=0; column<level-row; column++) |
| 315 | 237 |
{
|
| 316 |
addVertex(attribs, column, row , level, lonV1, lonV2, latV12, latV3, false);
|
|
| 317 |
if (column==0 && !(face==0 && row==0 ) ) repeatVertex(attribs,1);
|
|
| 318 |
addVertex(attribs, column, row+1, level, lonV1, lonV2, latV12, latV3, false);
|
|
| 238 |
addVertex(attribs, column, row , level, lonV1, lonV2, latV12, latV3); |
|
| 239 |
if (column==0 && !(face==0 && row==0 ) ) repeatVertex(attribs); |
|
| 240 |
addVertex(attribs, column, row+1, level, lonV1, lonV2, latV12, latV3); |
|
| 319 | 241 |
} |
| 320 | 242 |
|
| 321 |
addVertex(attribs, level-row, row , level, lonV1, lonV2, latV12, latV3, true);
|
|
| 322 |
if( row!=level-1 || face!=NUMFACES-1 ) repeatVertex(attribs,1);
|
|
| 243 |
addVertex(attribs, level-row, row , level, lonV1, lonV2, latV12, latV3); |
|
| 244 |
if( row!=level-1 || face!=NUMFACES-1 ) repeatVertex(attribs); |
|
| 323 | 245 |
} |
| 324 | 246 |
} |
| 325 | 247 |
|
Also available in: Unified diff
Simplify MeshSphere by deriving it not from a regular icosahedron (which has a problem of a wiggly seam line at the 'change of date' in the back of the sphere) but something that at the back has a straight vertical border between triangles: a diamond-like 16-faced solid.