Project

General

Profile

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

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

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 P = Math.PI;
35

    
36
  // An array of 16 entries, each describing a single face of the solid in an (admittedly) weird
37
  // fashion. Each face is a triangle, with 2 vertices on the same latitude.
38
  // Single row is (longitude of V1, longitude of V2, (common) latitude of V1 and V2, latitude of V3)
39
  // longitude of V3 is simply midpoint of V1 and V2 so we don't have to specify it here.
40

    
41
  private static final double FACES[][] =      {
42

    
43
      { 0.00*P, 0.25*P, 0.0, 0.5*P },
44
      { 0.25*P, 0.50*P, 0.0, 0.5*P },
45
      { 0.50*P, 0.75*P, 0.0, 0.5*P },
46
      { 0.75*P, 1.00*P, 0.0, 0.5*P },
47
      { 1.00*P, 1.25*P, 0.0, 0.5*P },
48
      { 1.25*P, 1.50*P, 0.0, 0.5*P },
49
      { 1.50*P, 1.75*P, 0.0, 0.5*P },
50
      { 1.75*P, 0.00*P, 0.0, 0.5*P },
51

    
52
      { 0.00*P, 0.25*P, 0.0,-0.5*P },
53
      { 0.25*P, 0.50*P, 0.0,-0.5*P },
54
      { 0.50*P, 0.75*P, 0.0,-0.5*P },
55
      { 0.75*P, 1.00*P, 0.0,-0.5*P },
56
      { 1.00*P, 1.25*P, 0.0,-0.5*P },
57
      { 1.25*P, 1.50*P, 0.0,-0.5*P },
58
      { 1.50*P, 1.75*P, 0.0,-0.5*P },
59
      { 1.75*P, 0.00*P, 0.0,-0.5*P },
60
                                               };
61
  private int currentVert;
62
  private int numVertices;
63

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

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

    
78
///////////////////////////////////////////////////////////////////////////////////////////////////
79

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

    
84
    if( currentVert>0 )
85
      {
86
      attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB  ] = attribs[VERT_ATTRIBS*(currentVert-1) + POS_ATTRIB  ];
87
      attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB+1] = attribs[VERT_ATTRIBS*(currentVert-1) + POS_ATTRIB+1];
88
      attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB+2] = attribs[VERT_ATTRIBS*(currentVert-1) + POS_ATTRIB+2];
89

    
90
      attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB  ] = attribs[VERT_ATTRIBS*(currentVert-1) + NOR_ATTRIB  ];
91
      attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB+1] = attribs[VERT_ATTRIBS*(currentVert-1) + NOR_ATTRIB+1];
92
      attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB+2] = attribs[VERT_ATTRIBS*(currentVert-1) + NOR_ATTRIB+2];
93

    
94
      attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB  ] = attribs[VERT_ATTRIBS*(currentVert-1) + INF_ATTRIB  ];
95
      attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB+1] = attribs[VERT_ATTRIBS*(currentVert-1) + INF_ATTRIB+1];
96
      attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB+2] = attribs[VERT_ATTRIBS*(currentVert-1) + INF_ATTRIB+2];
97

    
98
      attribs[VERT_ATTRIBS*currentVert + TEX_ATTRIB  ] = attribs[VERT_ATTRIBS*(currentVert-1) + TEX_ATTRIB  ];
99
      attribs[VERT_ATTRIBS*currentVert + TEX_ATTRIB+1] = attribs[VERT_ATTRIBS*(currentVert-1) + TEX_ATTRIB+1];
100

    
101
      currentVert++;
102
      }
103
    }
104

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

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

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

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

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

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

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

    
146
    return ret;
147
    }
148

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

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

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

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

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

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

    
182
    float x = (float)(cosLAT*sinLON / 2.0f);
183
    float y = (float)(sinLAT        / 2.0f);
184
    float z = (float)(cosLAT*cosLON / 2.0f);
185

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

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

    
191
    double texY = 0.5 + latitude/P;
192

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

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

    
208
    currentVert++;
209

    
210
    ////////////////////////////////////////////////////////////////////////////////////////////////
211
    // Problem: on the 'change of date' line in the back of the sphere, some triangles see texX
212
    // coords suddenly jump from 1-epsilon to 0, which looks like a seam with a narrow copy of
213
    // the whole texture there. Solution: remap texX to 1.0.
214
    ////////////////////////////////////////////////////////////////////////////////////////////////
215

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

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

    
229
///////////////////////////////////////////////////////////////////////////////////////////////////
230

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

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

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

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

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

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

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