Project

General

Profile

Download (17.7 KB) Statistics
| Branch: | Revision:

library / src / main / java / org / distorted / library / mesh / MeshSphere.java @ b72b8a3b

1
///////////////////////////////////////////////////////////////////////////////////////////////////
2
// Copyright 2018 Leszek Koltunski                                                               //
3
//                                                                                               //
4
// This file is part of Distorted.                                                               //
5
//                                                                                               //
6
// Distorted is free software: you can redistribute it and/or modify                             //
7
// it under the terms of the GNU General Public License as published by                          //
8
// the Free Software Foundation, either version 2 of the License, or                             //
9
// (at your option) any later version.                                                           //
10
//                                                                                               //
11
// Distorted is distributed in the hope that it will be useful,                                  //
12
// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
13
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
14
// GNU General Public License for more details.                                                  //
15
//                                                                                               //
16
// You should have received a copy of the GNU General Public License                             //
17
// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
18
///////////////////////////////////////////////////////////////////////////////////////////////////
19

    
20
package org.distorted.library.mesh;
21

    
22
///////////////////////////////////////////////////////////////////////////////////////////////////
23
/**
24
 * Create a Mesh which approximates a sphere.
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.
28
 */
29
public class MeshSphere extends MeshBase
30
  {
31
  private static final int NUMFACES = 20;
32
  private static final double sqrt2 = Math.sqrt(2.0);
33
  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

    
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.
40
  // Single row is (longitude of V1, longitude of V2, (common) latitude of V1 and V2, latitude of V3)
41
  // longitude of V3 is simply midpoint of V1 and V2 so we don't have to specify it here.
42

    
43
  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 },
67
                                               };
68
  private int currentVert;
69
  private int numVertices;
70

    
71
///////////////////////////////////////////////////////////////////////////////////////////////////
72
// Each of the 20 faces of the icosahedron requires (level*level + 4*level) vertices for the face
73
// 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.
75
// (level*level +4*level) because there are level*level little triangles, each requiring new vertex,
76
// plus 2 extra vertices to start off a row and 2 to move to the next row (or the next face in case
77
// 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

    
83
  private void computeNumberOfVertices(int level)
84
    {
85
    numVertices = NUMFACES*level*(level+4) -2  + 6*(level-1);
86
    currentVert = 0;
87
    }
88

    
89
///////////////////////////////////////////////////////////////////////////////////////////////////
90

    
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)
99
    {
100
    //android.util.Log.e("sphere", "repeat last!");
101

    
102
    if( currentVert>0 )
103
      {
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];
107

    
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];
111

    
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];
115

    
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];
118

    
119
      currentVert++;
120
      }
121
    }
122

    
123
///////////////////////////////////////////////////////////////////////////////////////////////////
124
// Supposed to return the latitude of the point between two points on the sphere with latitudes
125
// lat1 and lat2, so if for example quot=0.2, then it will return the latitude of something 20%
126
// along the way from lat1 to lat2.
127
//
128
// This is approximation only - in general it is of course not true that the midpoint of two points
129
// on a unit sphere with spherical coords (A1,B1) and (A2,B2) is ( (A1+A2)/2, (B1+B2)/2 ) - take
130
// (0,0) and (PI, epsilon) as a counterexample.
131
//
132
// Here however, the latitudes we are interested at are the latitudes of the vertices of a regular
133
// icosahedron - i.e. +=A and +=PI/2, whose longitudes are close, and we don't really care if the
134
// split into smaller triangles is exact.
135
//
136
// quot better be between 0.0 and 1.0.
137
// this is 'directed' i.e. from lat1 to lat2.
138

    
139
  private double midLatitude(double lat1, double lat2, double quot)
140
    {
141
    return lat1*(1.0-quot)+lat2*quot;
142
    }
143

    
144
///////////////////////////////////////////////////////////////////////////////////////////////////
145
// Same in case of longitude. This is for our needs exact, because we are ever only calling this with
146
// two longitudes of two vertices with the same latitude. Additional problem: things can wrap around
147
// the circle.
148
// this is 'undirected' i.e. we don't assume from lon1 to lon2 - just along the smaller arc joining
149
// lon1 to lon2.
150

    
151
  private double midLongitude(double lon1, double lon2, double quot)
152
    {
153
    double min, max;
154

    
155
    if( lon1<lon2 ) { min=lon1; max=lon2; }
156
    else            { min=lon2; max=lon1; }
157

    
158
    double diff = max-min;
159
    if( diff>P ) { diff=2*P-diff; min=max; }
160

    
161
    double ret = min+quot*diff;
162
    if( ret>=2*P ) ret-=2*P;
163

    
164
    return ret;
165
    }
166

    
167
///////////////////////////////////////////////////////////////////////////////////////////////////
168
// linear map (column,row, level):
169
//
170
// (      0,       0, level) -> (lonV1,latV12)
171
// (      0, level-1, level) -> (lonV3,latV3 )
172
// (level-1,       0, level) -> (lonV2,latV12)
173

    
174
  private void addVertex(float[] attribs, int column, int row, int level,
175
                         double lonV1, double lonV2, double latV12, double latV3, boolean lastInRow)
176
    {
177
    double quotX = (double)column/level;
178
    double quotY = (double)row   /level;
179
    double quotZ;
180

    
181
    if( latV12*latV3 < 0.0 )  // equatorial triangle
182
      {
183
      quotZ = quotX + 0.5*quotY;
184
      }
185
    else                      // polar triangle
186
      {
187
      quotZ = (quotY==1.0 ? 0.0 : quotX / (1.0-quotY));
188
      }
189

    
190
    double longitude = midLongitude(lonV1, lonV2, quotZ );
191
    double latitude  = midLatitude(latV12, latV3, quotY );
192

    
193
    //android.util.Log.e("sphere", "newVertex: long:"+lonPoint+" lat:"+latPoint+" column="+column+" row="+row);
194

    
195
    double sinLON = Math.sin(longitude);
196
    double cosLON = Math.cos(longitude);
197
    double sinLAT = Math.sin(latitude);
198
    double cosLAT = Math.cos(latitude);
199

    
200
    float x = (float)(cosLAT*sinLON / sqrt2);
201
    float y = (float)(sinLAT        / sqrt2);
202
    float z = (float)(cosLAT*cosLON / sqrt2);
203

    
204
    double texX = 0.5 + longitude/(2*P);
205
    if( texX>=1.0 ) texX-=1.0;
206

    
207
    //android.util.Log.e("tex", "longitude = "+((int)(180.0*longitude/P))+" texX="+texX );
208

    
209
    double texY = 0.5 + latitude/P;
210

    
211
    attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB  ] = x;  //
212
    attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB+1] = y;  //
213
    attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB+2] = z;  //
214
                                                           //  In case of this Mesh so it happens that
215
    attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB  ] = x;  //  the vertex coords, normal vector, and
216
    attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB+1] = y;  //  inflate vector have identical (x,y,z).
217
    attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB+2] = z;  //
218
                                                           //  TODO: think about some more efficient
219
    attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB  ] = x;  //  representation.
220
    attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB+1] = y;  //
221
    attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB+2] = z;  //
222

    
223
    attribs[VERT_ATTRIBS*currentVert + TEX_ATTRIB  ] = (float)texX;
224
    attribs[VERT_ATTRIBS*currentVert + TEX_ATTRIB+1] = (float)texY;
225

    
226
    currentVert++;
227

    
228
    ////////////////////////////////////////////////////////////////////////////////////////////////
229
    // 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 the two new ones' texX to 0.0.
236
    ////////////////////////////////////////////////////////////////////////////////////////////////
237

    
238
    if( currentVert>=3 )
239
      {
240
      double tex1 = attribs[VERT_ATTRIBS*(currentVert-2) + TEX_ATTRIB];
241
      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

    
245
      if( tex1!=tex2 || y1!=y2 )  // if the triangle is not degenerate
246
        {
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 )  // 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-1) + TEX_ATTRIB] = 0.0f;
272
                attribs[VERT_ATTRIBS*(currentVert-2) + TEX_ATTRIB] = 0.0f;
273
                attribs[VERT_ATTRIBS*(currentVert-3) + TEX_ATTRIB] = 1.0f;
274

    
275
                if (tex1 < tex2) attribs[VERT_ATTRIBS*(currentVert-4) + TEX_ATTRIB] = 1.0f;
276
                }
277
              if (2*column+row == level)    // not the last; just remap current vertex' texX to 1.0.
278
                {
279
                /*
280
                android.util.Log.e("sphere", "NOT LAST max diff exceeded: " + convertAng(longitude) + " " + convertAng(latitude) + " texX=" + texX);
281
                android.util.Log.d("sphere", " tex1=" + tex1 + " tex2=" + tex2 + " y1=" + y1 + " y2=" + y2);
282
                android.util.Log.d("sphere", " latV12=" + latV12 + " latV3=" + latV3);
283
                */
284
                attribs[VERT_ATTRIBS*(currentVert-1) + TEX_ATTRIB] = 1.0f;
285
                }
286
              }
287
            else // bottom triangle
288
              {
289
              /*
290
              android.util.Log.e("sphere", "BOTTOM LAST max diff exceeded: " + convertAng(longitude) + " " + convertAng(latitude) + " texX=" + texX);
291
              android.util.Log.d("sphere", " tex1=" + tex1 + " tex2=" + tex2 + " y1=" + y1 + " y2=" + y2);
292
              android.util.Log.d("sphere", " latV12=" + latV12 + " latV3=" + latV3);
293
              */
294
              repeatVertex(attribs, 2);
295
              repeatVertex(attribs, 2);
296

    
297
              attribs[VERT_ATTRIBS*(currentVert-3) + TEX_ATTRIB] = 1.0f;
298
              }
299
            }
300
          }
301
        }
302
      }
303

    
304
    ////////////////////////////////////////////////////////////////////////////////////////////////
305
    // End problem
306
    ////////////////////////////////////////////////////////////////////////////////////////////////
307
    }
308

    
309
///////////////////////////////////////////////////////////////////////////////////////////////////
310

    
311
  private void buildFace(float[] attribs, int level, int face, double lonV1, double lonV2, double latV12, double latV3)
312
    {
313
    for(int row=0; row<level; row++)
314
      {
315
      for (int column=0; column<level-row; column++)
316
        {
317
        addVertex(attribs, column, row  , level, lonV1, lonV2, latV12, latV3, false);
318
        if (column==0 && !(face==0 && row==0 ) ) repeatVertex(attribs,1);
319
        addVertex(attribs, column, row+1, level, lonV1, lonV2, latV12, latV3, false);
320
        }
321

    
322
      addVertex(attribs, level-row, row , level, lonV1, lonV2, latV12, latV3, true);
323
      if( row!=level-1 || face!=NUMFACES-1 ) repeatVertex(attribs,1);
324
      }
325
    }
326

    
327
///////////////////////////////////////////////////////////////////////////////////////////////////
328
// PUBLIC API
329
///////////////////////////////////////////////////////////////////////////////////////////////////
330
  /**
331
   * Creates the underlying grid of vertices with the usual attribs which approximates a sphere.
332
   * <p>
333
   * When level=1, it outputs the 12 vertices of a regular icosahedron.
334
   * When level=N, it divides each of the 20 icosaherdon's triangular faces into N^2 smaller triangles
335
   * (by dividing each side into N equal segments) and 'inflates' the internal vertices so that they
336
   * touch the sphere.
337
   *
338
   * @param level Specifies the approximation level. Valid values: level &ge; 1
339
   */
340
  public MeshSphere(int level)
341
    {
342
    super(1.0f);
343

    
344
    computeNumberOfVertices(level);
345
    float[] attribs= new float[VERT_ATTRIBS*numVertices];
346

    
347
    for(int face=0; face<NUMFACES; face++ )
348
      {
349
      buildFace(attribs, level, face, FACES[face][0], FACES[face][1], FACES[face][2], FACES[face][3]);
350
      }
351

    
352
    if( currentVert!=numVertices )
353
      android.util.Log.d("MeshSphere", "currentVert= " +currentVert+" numVertices="+numVertices );
354

    
355
    setAttribs(attribs);
356
    }
357
  }
(5-5/5)