Project

General

Profile

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

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

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
  private void computeNumberOfVertices(int level)
80
    {
81
    numVertices = NUMFACES*level*(level+4) -2;
82
    currentVert = 0;
83
    }
84

    
85
///////////////////////////////////////////////////////////////////////////////////////////////////
86
// (longitude,latitude) - spherical coordinates of a point on a unit sphere.
87
// Cartesian (0,0,1) - i.e. the point of the sphere closest to the camera - is spherical (0,0).
88

    
89
  private void addVertex( double longitude, double latitude, float[] attribs)
90
    {
91
    double sinLON = Math.sin(longitude);
92
    double cosLON = Math.cos(longitude);
93
    double sinLAT = Math.sin(latitude);
94
    double cosLAT = Math.cos(latitude);
95

    
96
    float x = (float)(cosLAT*sinLON / sqrt2);
97
    float y = (float)(sinLAT        / sqrt2);
98
    float z = (float)(cosLAT*cosLON / sqrt2);
99

    
100
    double texX = 0.5 + longitude/(2*P);
101
    if( texX>=1.0 ) texX-=1.0;
102

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

    
105
    double texY = 0.5 + latitude/P;
106

    
107
    attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB  ] = x;  //
108
    attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB+1] = y;  //
109
    attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB+2] = z;  //
110
                                                           //  In case of this Mesh so it happens that
111
    attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB  ] = x;  //  the vertex coords, normal vector, and
112
    attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB+1] = y;  //  inflate vector have identical (x,y,z).
113
    attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB+2] = z;  //
114
                                                           //  TODO: think about some more efficient
115
    attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB  ] = x;  //  representation.
116
    attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB+1] = y;  //
117
    attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB+2] = z;  //
118

    
119
    attribs[VERT_ATTRIBS*currentVert + TEX_ATTRIB  ] = (float)texX;
120
    attribs[VERT_ATTRIBS*currentVert + TEX_ATTRIB+1] = (float)texY;
121

    
122
    currentVert++;
123
    }
124

    
125

    
126
///////////////////////////////////////////////////////////////////////////////////////////////////
127

    
128
  private void repeatLast(float[] attribs)
129
    {
130
    //android.util.Log.e("sphere", "repeat last!");
131

    
132
    if( currentVert>0 )
133
      {
134
      attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB  ] = attribs[VERT_ATTRIBS*(currentVert-1) + POS_ATTRIB  ];
135
      attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB+1] = attribs[VERT_ATTRIBS*(currentVert-1) + POS_ATTRIB+1];
136
      attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB+2] = attribs[VERT_ATTRIBS*(currentVert-1) + POS_ATTRIB+2];
137

    
138
      attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB  ] = attribs[VERT_ATTRIBS*(currentVert-1) + NOR_ATTRIB  ];
139
      attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB+1] = attribs[VERT_ATTRIBS*(currentVert-1) + NOR_ATTRIB+1];
140
      attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB+2] = attribs[VERT_ATTRIBS*(currentVert-1) + NOR_ATTRIB+2];
141

    
142
      attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB  ] = attribs[VERT_ATTRIBS*(currentVert-1) + INF_ATTRIB  ];
143
      attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB+1] = attribs[VERT_ATTRIBS*(currentVert-1) + INF_ATTRIB+1];
144
      attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB+2] = attribs[VERT_ATTRIBS*(currentVert-1) + INF_ATTRIB+2];
145

    
146
      attribs[VERT_ATTRIBS*currentVert + TEX_ATTRIB  ] = attribs[VERT_ATTRIBS*(currentVert-1) + TEX_ATTRIB  ];
147
      attribs[VERT_ATTRIBS*currentVert + TEX_ATTRIB+1] = attribs[VERT_ATTRIBS*(currentVert-1) + TEX_ATTRIB+1];
148

    
149
      currentVert++;
150
      }
151
    }
152

    
153
///////////////////////////////////////////////////////////////////////////////////////////////////
154
// Supposed to return the latitude of the point between two points on the sphere with latitudes
155
// lat1 and lat2, so if for example quot=0.2, then it will return the latitude of something 20%
156
// along the way from lat1 to lat2.
157
//
158
// This is approximation only - in general it is of course not true that the midpoint of two points
159
// on a unit sphere with spherical coords (A1,B1) and (A2,B2) is ( (A1+A2)/2, (B1+B2)/2 ) - take
160
// (0,0) and (PI, epsilon) as a counterexample.
161
//
162
// Here however, the latitudes we are interested at are the latitudes of the vertices of a regular
163
// icosahedron - i.e. +=A and +=PI/2, whose longitudes are close, and we don't really care if the
164
// split into smaller triangles is exact.
165
//
166
// quot better be between 0.0 and 1.0.
167
// this is 'directed' i.e. from lat1 to lat2.
168

    
169
  private double midLatitude(double lat1, double lat2, double quot)
170
    {
171
    return lat1*(1.0-quot)+lat2*quot;
172
    }
173

    
174
///////////////////////////////////////////////////////////////////////////////////////////////////
175
// Same in case of longitude. This is for our needs exact, because we are ever only calling this with
176
// two longitudes of two vertices with the same latitude. Additional problem: things can wrap around
177
// the circle.
178
// this is 'undirected' i.e. we don't assume from lon1 to lon2 - just along the smaller arc joining
179
// lon1 to lon2.
180

    
181
  private double midLongitude(double lon1, double lon2, double quot)
182
    {
183
    double min, max;
184

    
185
    if( lon1<lon2 ) { min=lon1; max=lon2; }
186
    else            { min=lon2; max=lon1; }
187

    
188
    double diff = max-min;
189
    if( diff>P ) { diff=2*P-diff; min=max; }
190

    
191
    double ret = min+quot*diff;
192
    if( ret>=2*P ) ret-=2*P;
193

    
194
    return ret;
195
    }
196

    
197
///////////////////////////////////////////////////////////////////////////////////////////////////
198
// linear map (column,row, level):
199
//
200
// (      0,       0, level) -> (lonV1,latV12)
201
// (      0, level-1, level) -> (lonV3,latV3 )
202
// (level-1,       0, level) -> (lonV2,latV12)
203

    
204
  private void newVertex(float[] attribs, int column, int row, int level,
205
                         double lonV1, double lonV2, double latV12, double latV3)
206
    {
207
    double quotX = (double)column/level;
208
    double quotY = (double)row   /level;
209
    double quotZ;
210

    
211
    if( latV12*latV3 < 0.0 )  // equatorial triangle
212
      {
213
      quotZ = quotX + 0.5*quotY;
214
      }
215
    else                      // polar triangle
216
      {
217
      quotZ = (quotY==1.0 ? 0.0 : quotX / (1.0-quotY));
218
      }
219

    
220
    double lonPoint = midLongitude(lonV1, lonV2, quotZ );
221
    double latPoint = midLatitude(latV12, latV3, quotY );
222

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

    
225
    addVertex(lonPoint,latPoint,attribs);
226
    }
227

    
228
///////////////////////////////////////////////////////////////////////////////////////////////////
229

    
230
  private void buildFace(float[] attribs, int level, int face, double lonV1, double lonV2, double latV12, double latV3)
231
    {
232
    for(int row=0; row<level; row++)
233
      {
234
      for (int column=0; column<level-row; column++)
235
        {
236
        newVertex(attribs, column, row  , level, lonV1, lonV2, latV12, latV3);
237
        if (column==0 && !(face==0 && row==0 ) ) repeatLast(attribs);
238
        newVertex(attribs, column, row+1, level, lonV1, lonV2, latV12, latV3);
239
        }
240

    
241
      newVertex(attribs, level-row, row , level, lonV1, lonV2, latV12, latV3);
242
      if( row!=level-1 || face!=NUMFACES-1 ) repeatLast(attribs);
243
      }
244
    }
245

    
246
///////////////////////////////////////////////////////////////////////////////////////////////////
247
// PUBLIC API
248
///////////////////////////////////////////////////////////////////////////////////////////////////
249
  /**
250
   * Creates the underlying grid of vertices with the usual attribs which approximates a sphere.
251
   * <p>
252
   * When level=1, it outputs the 12 vertices of a regular icosahedron.
253
   * When level=N, it divides each of the 20 icosaherdon's triangular faces into N^2 smaller triangles
254
   * (by dividing each side into N equal segments) and 'inflates' the internal vertices so that they
255
   * touch the sphere.
256
   *
257
   * @param level Specifies the approximation level. Valid values: level &ge; 1
258
   */
259
  public MeshSphere(int level)
260
    {
261
    super(1.0f);
262

    
263
    computeNumberOfVertices(level);
264
    float[] attribs= new float[VERT_ATTRIBS*numVertices];
265

    
266
    for(int face=0; face<NUMFACES; face++ )
267
      {
268
      buildFace(attribs, level, face, FACES[face][0], FACES[face][1], FACES[face][2], FACES[face][3]);
269
      }
270

    
271
    if( currentVert!=numVertices )
272
      android.util.Log.d("MeshSphere", "currentVert= " +currentVert+" numVertices="+numVertices );
273

    
274
    setAttribs(attribs);
275
    }
276
  }
(5-5/5)