001/*
002 * Renderer Models. The MIT License.
003 * Copyright (c) 2022 rlkraft@pnw.edu
004 * See LICENSE for details.
005*/
006
007package renderer.models_L;
008
009import renderer.scene.*;
010import renderer.scene.primitives.*;
011import renderer.scene.util.MeshMaker;
012
013/**
014   Create a wireframe model of a partial sphere centered at the origin
015<p>
016   See <a href="https://en.wikipedia.org/wiki/Sphere" target="_top">
017                https://en.wikipedia.org/wiki/Sphere</a>
018<p>
019   By a partial sphere we mean cutting a hole in the sphere around
020   either the north or the south pole (that is, removing a spherical
021   cap from either the top or bottom of the sphere) and also cutting
022   from the sphere a spherical wedge between two lines of longitude.
023<p>
024   Notice that we can use this model to both model a spherical wedge
025   and to model a sphere with a spherical wedge removed from it.
026<p>
027   Similarly, we can use this model to both model a spherical cap
028   and to model a sphere with a spherical cap removed from it.
029<p>
030   See <a href="https://en.wikipedia.org/wiki/Spherical_cap" target="_top">
031                https://en.wikipedia.org/wiki/Spherical_cap</a>
032<p>
033   See <a href="https://en.wikipedia.org/wiki/Spherical_segment" target="_top">
034                https://en.wikipedia.org/wiki/Spherical_segment</a>
035<p>
036   See <a href="https://en.wikipedia.org/wiki/Spherical_wedge" target="_top">
037                https://en.wikipedia.org/wiki/Spherical_wedge</a>
038<p>
039   The whole sphere of radius {@code r} is the surface of revolution generated
040   by revolving the right half-circle in the xy-plane with radius {@code r} and
041   center {@code (0,0,0)} all the way around the y-axis.
042<p>
043   Here are parametric equations for the right half-circle in the xy-plane with
044   radius {@code r} and center {@code (0,0,0)}, parameterized from the top down.
045   <pre>{@code
046      x(phi) = r * sin(phi)  \
047      y(phi) = r * cos(phi)   |-  0 <= phi <= PI
048      z(phi) = 0             /
049   }</pre>
050   Here is the 3D rotation matrix that rotates around the y-axis
051   by {@code theta} radians, {@code 0 <= theta <= 2*PI}
052   <pre>{@code
053      [ cos(theta)   0   sin(theta)]
054      [     0        1       0     ]
055      [-sin(theta)   0   cos(theta)]
056   }</pre>
057   If we multiply the rotation matrix with the half-circle
058   parameterization, we get a parameterization of the sphere.
059   <pre>{@code
060      [ cos(theta)   0   sin(theta)]   [r * sin(phi)]
061      [     0        1       0     ] * [r * cos(phi)]
062      [-sin(theta)   0   cos(theta)]   [     0      ]
063
064      = ( r * sin(phi) * cos(theta).    \
065          r * cos(phi),                  |- 0<=theta<=2*PI,  0<=phi<=PI
066         -r * sin(phi) * sin(theta) )   /
067   }</pre>
068   See
069     <a href="https://en.wikipedia.org/wiki/Sphere#Equations_in_three-dimensional_space" target="_top">
070              https://en.wikipedia.org/wiki/Sphere#Equations_in_three-dimensional_space</a>
071
072   @see Sphere
073   @see CircleSector
074   @see DiskSector
075   @see RingSector
076   @see ConeSector
077   @see CylinderSector
078   @see TorusSector
079*/
080public class SphereSector extends Model implements MeshMaker
081{
082   public final double r;
083   public final double theta1;
084   public final double theta2;
085   public final double phi1;
086   public final double phi2;
087   public final int n;
088   public final int k;
089
090   /**
091      Create half of a sphere of radius 1 centered at the origin.
092   */
093   public SphereSector()
094   {
095      this(1, Math.PI/2, 3*Math.PI/2, 15, 8);
096   }
097
098
099   /**
100      Create a part of the sphere of radius r centered at the origin.
101   <p>
102      If {@code theta1 > 0} and {@code theta1 < theta2 < 2*PI}, then a
103      spherical wedge is removed from the model. In other words, the
104      (partial) circles of latitude in the model extend from angle
105      {@code theta1} to angle {@code theta2}.
106   <p>
107      The last two parameters determine the number of half circles of
108      longitude and the number of (partial) circles of latitude in the model.
109   <p>
110      If there are {@code k} half circles of longitude, then each (partial)
111      circle of latitude will have {@code k-1} line segments.
112      If there are {@code n} circles of latitude, then each half circle
113      of longitude will have {@code n+1} line segments.
114   <p>
115      There must be at least four half circles of longitude and
116      at least one circle of latitude.
117
118      @param r       radius of the sphere
119      @param theta1  beginning longitude angle (in radians) of the spherical wedge
120      @param theta2  ending longitude angle (in radians) of the spherical wedge
121      @param n       number of circles of latitude
122      @param k       number of lines of longitude, not counting the edges of a spherical wedge
123      @throws IllegalArgumentException if {@code n} is less than 3
124      @throws IllegalArgumentException if {@code k} is less than 4
125   */
126   public SphereSector(final double r,
127                       final double theta1, final double theta2,
128                       final int n, final int k)
129   {
130      this(r, theta1, theta2, 0, Math.PI, n+2, k);
131   }
132
133
134   /**
135      Create a part of the sphere of radius r centered at the origin.
136   <p>
137      If {@code phi1 > 0}, then there is hole in the sphere around its
138      north pole. Similarly, if {@code phi2 < PI}, then there is a hole
139      in the sphere around its south pole. In other words, in spherical
140      coordinates, lines of longitude in the model extend from angle
141      {@code phi1} to angle {@code phi2}.
142   <p>
143      If {@code theta1 > 0} and {@code theta1 < theta2 < 2*PI}, then a
144      spherical wedge is removed from the model. In other words, the
145      (partial) circles of latitude in the model extend from angle
146      {@code theta1} to angle {@code theta2}.
147   <p>
148      The last two parameters determine the number of lines of longitude
149      and the number of (partial) circles of latitude in the model.
150   <p>
151      If there are {@code k} lines of longitude, then each (partial)
152      circle of latitude will have {@code k-1} line segments.
153      If there are {@code n} circles of latitude (including the edges
154      of the removed spherical caps), then each line of longitude will
155      have {@code n-1} line segments.
156   <p>
157      There must be at least four lines of longitude and at least
158      three circles of latitude.
159
160      @param r       radius of the sphere
161      @param theta1  beginning longitude angle (in radians) of the spherical wedge
162      @param theta2  ending longitude angle (in radians) of the spherical wedge
163      @param phi1    beginning latitude angle (in radians) of the spherical segment
164      @param phi2    ending latitude angle (in radians) of the spherical segment
165      @param n       number of circles of latitude, not counting the edges of a spherical segment
166      @param k       number of lines of longitude, not counting one edge of a spherical wedge
167      @throws IllegalArgumentException if {@code n} is less than 3
168      @throws IllegalArgumentException if {@code k} is less than 4
169   */
170   public SphereSector(final double r,
171                       double theta1, double theta2,
172                       final double phi1, final double phi2,
173                       final int n, final int k)
174   {
175      super(String.format("Sphere Sector(%.2f,%.2f,%.2f,%.2f,%.2f,%d,%d)",
176                                        r, theta1, theta2, phi1, phi2, n, k));
177
178      if (n < 3)
179         throw new IllegalArgumentException("n must be greater than 2");
180      if (k < 4)
181         throw new IllegalArgumentException("k must be greater than 3");
182
183      theta1 = theta1 % (2*Math.PI);
184      theta2 = theta2 % (2*Math.PI);
185      if (theta1 < 0) theta1 = 2*Math.PI + theta1;
186      if (theta2 < 0) theta2 = 2*Math.PI + theta2;
187      if (theta2 <= theta1) theta2 = theta2 + 2*Math.PI;
188
189      this.r = r;
190      this.theta1 = theta1;
191      this.theta2 = theta2;
192      this.phi1 = phi1;
193      this.phi2 = phi2;
194      this.n = n;
195      this.k = k;
196
197      // Create the sphere section's geometry.
198
199      final double deltaPhi = (phi2 - phi1) / (n - 1),
200                   deltaTheta = (theta2 - theta1) / (k - 1);
201
202      // An array of vertices to be used to create line segments.
203      final Vertex[][] v = new Vertex[n][k];
204
205      // Create all the vertices.
206      for (int j = 0; j < k; ++j) // choose an angle of longitude
207      {
208         final double c1 = Math.cos(theta1 + j * deltaTheta),
209                      s1 = Math.sin(theta1 + j * deltaTheta);
210         for (int i = 0; i < n; ++i) // choose an angle of latitude
211         {
212            final double c2 = Math.cos(phi1 + i * deltaPhi),
213                         s2 = Math.sin(phi1 + i * deltaPhi);
214            v[i][j] = new Vertex( r * s2 * c1,
215                                  r * c2,
216                                 -r * s2 * s1 );
217         }
218      }
219
220      // Add all of the vertices to this model.
221      for (int i = 0; i < n; ++i)
222      {
223         for (int j = 0; j < k; ++j)
224         {
225            addVertex( v[i][j] );
226         }
227      }
228
229      // Create the horizontal (partial) circles of latitude around the sphere.
230      for (int i = 0; i < n; ++i)
231      {
232         for (int j = 0; j < k - 1; ++j)
233         {  //                                v[i][j]        v[i][j+1]
234            addPrimitive(new LineSegment( (i * k) + j,  (i * k) + (j+1) ));
235         }
236      }
237
238      // Create the vertical lines of longitude from the top edge to the bottom edge.
239      for (int j = 0; j < k; ++j)
240      {
241         for (int i = 0; i < n - 1; ++i)
242         {  //                                v[i][j]        v[i+1][j]
243            addPrimitive(new LineSegment( (i * k) + j, ((i+1) * k) + j ));
244         }
245      }
246   }
247
248
249
250   // Implement the MeshMaker interface (three methods).
251   @Override public int getHorzCount() {return n;}
252
253   @Override public int getVertCount() {return k;}
254
255   @Override
256   public SphereSector remake(final int n, final int k)
257   {
258      return new SphereSector(this.r,
259                              this.theta1, this.theta2,
260                              this.phi1, this.phi2,
261                              n, k);
262   }
263}//SphereSector