Project

General

Profile

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

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

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 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.
30
 */
31
public class MeshSphere extends MeshBase
32
  {
33
  private static final int NUMFACES = 16;
34
  private static final double sqrt2 = Math.sqrt(2.0);
35
  private static final double P = Math.PI;
36

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

    
42
  private static final double FACES[][] =      {
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 },
61
                                               };
62
  private int currentVert;
63
  private int numVertices;
64

    
65
///////////////////////////////////////////////////////////////////////////////////////////////////
66
// Each of the 16 faces of the solid requires (level*level + 4*level) vertices for the face
67
// itself and a join to the next face (which requires 2 vertices). We don't need the join in case
68
// of the last, 16th face, thus the -2.
69
// (level*level +4*level) because there are level*level little triangles, each requiring new vertex,
70
// plus 2 extra vertices to start off a row and 2 to move to the next row (or the next face in case
71
// of the last row) and there are 'level' rows.
72

    
73
  private void computeNumberOfVertices(int level)
74
    {
75
    numVertices = NUMFACES*level*(level+4) -2;
76
    currentVert = 0;
77
    }
78

    
79
///////////////////////////////////////////////////////////////////////////////////////////////////
80

    
81
  private void repeatVertex(float[] attribs)
82
    {
83
    //android.util.Log.e("sphere", "repeat last!");
84

    
85
    if( currentVert>0 )
86
      {
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];
90

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

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

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

    
102
      currentVert++;
103
      }
104
    }
105

    
106
///////////////////////////////////////////////////////////////////////////////////////////////////
107
// Supposed to return the latitude of the point between two points on the sphere with latitudes
108
// lat1 and lat2, so if for example quot=0.2, then it will return the latitude of something 20%
109
// along the way from lat1 to lat2.
110
//
111
// This is approximation only - in general it is of course not true that the midpoint of two points
112
// on a unit sphere with spherical coords (A1,B1) and (A2,B2) is ( (A1+A2)/2, (B1+B2)/2 ) - take
113
// (0,0) and (PI, epsilon) as a counterexample.
114
//
115
// Here however, the latitudes we are interested at are the latitudes of the vertices of a regular
116
// icosahedron - i.e. +=A and +=PI/2, whose longitudes are close, and we don't really care if the
117
// split into smaller triangles is exact.
118
//
119
// quot better be between 0.0 and 1.0.
120
// this is 'directed' i.e. from lat1 to lat2.
121

    
122
  private double midLatitude(double lat1, double lat2, double quot)
123
    {
124
    return lat1*(1.0-quot)+lat2*quot;
125
    }
126

    
127
///////////////////////////////////////////////////////////////////////////////////////////////////
128
// Same in case of longitude. This is for our needs exact, because we are ever only calling this with
129
// two longitudes of two vertices with the same latitude. Additional problem: things can wrap around
130
// the circle.
131
// this is 'undirected' i.e. we don't assume from lon1 to lon2 - just along the smaller arc joining
132
// lon1 to lon2.
133

    
134
  private double midLongitude(double lon1, double lon2, double quot)
135
    {
136
    double min, max;
137

    
138
    if( lon1<lon2 ) { min=lon1; max=lon2; }
139
    else            { min=lon2; max=lon1; }
140

    
141
    double diff = max-min;
142
    if( diff>P ) { diff=2*P-diff; min=max; }
143

    
144
    double ret = min+quot*diff;
145
    if( ret>=2*P ) ret-=2*P;
146

    
147
    return ret;
148
    }
149

    
150
///////////////////////////////////////////////////////////////////////////////////////////////////
151
// linear map (column,row, level):
152
//
153
// (      0,       0, level) -> (lonV1,latV12)
154
// (      0, level-1, level) -> (lonV3,latV3 )
155
// (level-1,       0, level) -> (lonV2,latV12)
156

    
157
  private void addVertex(float[] attribs, int column, int row, int level,
158
                         double lonV1, double lonV2, double latV12, double latV3)
159
    {
160
    double quotX = (double)column/level;
161
    double quotY = (double)row   /level;
162
    double quotZ;
163

    
164
    if( latV12*latV3 < 0.0 )  // equatorial triangle
165
      {
166
      quotZ = quotX + 0.5*quotY;
167
      }
168
    else                      // polar triangle
169
      {
170
      quotZ = (quotY==1.0 ? 0.5 : quotX / (1.0-quotY));
171
      }
172

    
173
    double longitude = midLongitude(lonV1, lonV2, quotZ );
174
    double latitude  = midLatitude(latV12, latV3, quotY );
175

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

    
178
    double sinLON = Math.sin(longitude);
179
    double cosLON = Math.cos(longitude);
180
    double sinLAT = Math.sin(latitude);
181
    double cosLAT = Math.cos(latitude);
182

    
183
    float x = (float)(cosLAT*sinLON / sqrt2);
184
    float y = (float)(sinLAT        / sqrt2);
185
    float z = (float)(cosLAT*cosLON / sqrt2);
186

    
187
    double texX = 0.5 + longitude/(2*P);
188
    if( texX>=1.0 ) texX-=1.0;
189

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

    
192
    double texY = 0.5 + latitude/P;
193

    
194
    attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB  ] = x;  //
195
    attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB+1] = y;  //
196
    attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB+2] = z;  //
197
                                                           //  In case of this Mesh so it happens that
198
    attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB  ] = x;  //  the vertex coords, normal vector, and
199
    attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB+1] = y;  //  inflate vector have identical (x,y,z).
200
    attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB+2] = z;  //
201
                                                           //  TODO: think about some more efficient
202
    attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB  ] = x;  //  representation.
203
    attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB+1] = y;  //
204
    attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB+2] = z;  //
205

    
206
    attribs[VERT_ATTRIBS*currentVert + TEX_ATTRIB  ] = (float)texX;
207
    attribs[VERT_ATTRIBS*currentVert + TEX_ATTRIB+1] = (float)texY;
208

    
209
    currentVert++;
210

    
211
    ////////////////////////////////////////////////////////////////////////////////////////////////
212
    // Problem: on the 'change of date' line in the back of the sphere, some triangles see texX
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.
215
    ////////////////////////////////////////////////////////////////////////////////////////////////
216

    
217
    if( currentVert>=3 && texX==0.0 )
218
      {
219
      double tex1 = attribs[VERT_ATTRIBS*(currentVert-2) + TEX_ATTRIB];
220
      double tex2 = attribs[VERT_ATTRIBS*(currentVert-3) + TEX_ATTRIB];
221

    
222
      // if the triangle is not degenerate and last vertex was on the western hemisphere
223
      if( tex1!=tex2 && tex1>0.5 )
224
        {
225
        attribs[VERT_ATTRIBS*(currentVert-1) + TEX_ATTRIB] = 1.0f;
226
        }
227
      }
228
    }
229

    
230
///////////////////////////////////////////////////////////////////////////////////////////////////
231

    
232
  private void buildFace(float[] attribs, int level, int face, double lonV1, double lonV2, double latV12, double latV3)
233
    {
234
    for(int row=0; row<level; row++)
235
      {
236
      for (int column=0; column<level-row; column++)
237
        {
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);
241
        }
242

    
243
      addVertex(attribs, level-row, row , level, lonV1, lonV2, latV12, latV3);
244
      if( row!=level-1 || face!=NUMFACES-1 ) repeatVertex(attribs);
245
      }
246
    }
247

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

    
265
    computeNumberOfVertices(level);
266
    float[] attribs= new float[VERT_ATTRIBS*numVertices];
267

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

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

    
276
    setAttribs(attribs);
277
    }
278
  }
(5-5/5)