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 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   A sphere of radius {@code r} is the surface of revolution generated by
020   revolving a half-circle in the xy-plane with radius {@code r} and center
021   {@code (0,0,0)} around the y-axis.
022<p>
023   Here are parametric equations for the right half-circle in the xy-plane with
024   radius {@code r} and center {@code (0,0,0)}, parameterized from the top down.
025   <pre>{@code
026      x(phi) = r * sin(phi)  \
027      y(phi) = r * cos(phi)   |-  0 <= phi <= PI
028      z(phi) = 0             /
029   }</pre>
030   Here is the 3D rotation matrix that rotates around the y-axis
031   by {@code theta} radians, {@code 0 <= theta <= 2*PI}
032   <pre>{@code
033      [ cos(theta)   0   sin(theta)]
034      [     0        1       0     ]
035      [-sin(theta)   0   cos(theta)]
036   }</pre>
037   If we multiply the rotation matrix with the half-circle
038   parameterization, we get a parameterization of the sphere.
039   <pre>{@code
040      [ cos(theta)   0   sin(theta)]   [r * sin(phi)]
041      [     0        1       0     ] * [r * cos(phi)]
042      [-sin(theta)   0   cos(theta)]   [     0      ]
043
044      = ( r * sin(phi) * cos(theta).    \
045          r * cos(phi),                  |- 0<=theta<=2*PI,  0<=phi<=PI
046         -r * sin(phi) * sin(theta) )   /
047   }</pre>
048   See
049     <a href="https://en.wikipedia.org/wiki/Sphere#Equations_in_three-dimensional_space" target="_top">
050              https://en.wikipedia.org/wiki/Sphere#Equations_in_three-dimensional_space</a>
051
052   @see SphereSector
053*/
054public class Sphere extends Model implements MeshMaker
055{
056   public final double r;
057   public final int n;
058   public final int k;
059
060   /**
061      Create a sphere of radius 1 centered at the origin.
062   */
063   public Sphere( )
064   {
065      this(1, 15, 16);
066   }
067
068
069   /**
070      Create a sphere of radius {@code r} centered at the origin.
071
072      @param r  radius of the sphere
073   */
074   public Sphere(final double r)
075   {
076      this(r, 15, 16);
077   }
078
079
080   /**
081      Create a sphere of radius {@code r} centered at the origin.
082   <p>
083      The last two parameters determine the number of half circles
084      of longitude and the number of circles of latitude in the model.
085   <p>
086      If there are {@code k} half circles of longitude, then each circle
087      of latitude will have {@code k} line segments.
088      If there are {@code n} circles of latitude, then each half circle
089      of longitude will have {@code n+1} line segments.
090   <p>
091      There must be at least three half circles of longitude and
092      at least one circle of latitude.
093
094      @param r  radius of the sphere
095      @param n  number of circles of latitude
096      @param k  number of half circles of longitude
097      @throws IllegalArgumentException if {@code n} is less than 1
098      @throws IllegalArgumentException if {@code k} is less than 3
099   */
100   public Sphere(final double r, final int n, final int k)
101   {
102      super(String.format("Sphere(%.2f,%d,%d)", r, n, k));
103
104      if (n < 1)
105         throw new IllegalArgumentException("n must be greater than 0");
106      if (k < 3)
107         throw new IllegalArgumentException("k must be greater than 2");
108
109      this.r = r;
110      this.n = n;
111      this.k = k;
112
113      // Create the sphere's geometry.
114
115      final double deltaPhi = Math.PI / (n + 1),
116                   deltaTheta = (2 * Math.PI) / k;
117
118      // An array of vertices to be used to create line segments.
119      final Vertex[][] v = new Vertex[n][k];
120
121      // Create all the vertices.
122      for (int j = 0; j < k; ++j) // choose an angle of longitude
123      {
124         final double c1 = Math.cos(j * deltaTheta),
125                      s1 = Math.sin(j * deltaTheta);
126         for (int i = 0; i < n; ++i) // choose an angle of latitude
127         {
128            final double c2 = Math.cos(deltaPhi + i * deltaPhi),
129                         s2 = Math.sin(deltaPhi + i * deltaPhi);
130            v[i][j] = new Vertex( r * s2 * c1,
131                                  r * c2,
132                                 -r * s2 * s1 );
133         }
134      }
135      final Vertex northPole = new Vertex(0,  r, 0),
136                   southPole = new Vertex(0, -r, 0);
137
138      // Add all of the vertices to this model.
139      for (int i = 0; i < n; ++i)
140      {
141         for (int j = 0; j < k; ++j)
142         {
143            addVertex( v[i][j] );
144         }
145      }
146      addVertex(northPole,
147                southPole);
148      final int northPoleIndex = n * k,
149                southPoleIndex = northPoleIndex + 1;
150
151      // Create the horizontal circles of latitude around the sphere.
152      for (int i = 0; i < n; ++i)
153      {
154         for (int j = 0; j < k - 1; ++j)
155         {  //                                v[i][j]       v[i][j+1]
156            addPrimitive(new LineSegment( (i * k) + j,  (i * k) + (j+1) ));
157         }
158         // close the circle
159         addPrimitive(new LineSegment( (i * k) + (k-1), (i * k) + 0 ));
160      }  //                                v[i][k-1]        v[i][0]
161
162      // Create the vertical half-circles of longitude from north to south pole.
163      for (int j = 0; j < k; ++j)
164      {  //                                                v[0][j]
165         addPrimitive(new LineSegment( northPoleIndex, (0 * k) + j ));
166
167         for (int i = 0; i < n - 1; ++i)
168         {  //                                v[i][j]      v[i+1][j]
169            addPrimitive(new LineSegment( (i * k) + j, ((i+1) * k) + j ));
170         }
171         //                                 v[n-1][j]
172         addPrimitive(new LineSegment( ((n-1) * k) + j, southPoleIndex ));
173      }
174   }
175
176
177
178   // Implement the MeshMaker interface (three methods).
179   @Override public int getHorzCount() {return n;}
180
181   @Override public int getVertCount() {return k;}
182
183   @Override
184   public Sphere remake(final int n, final int k)
185   {
186      return new Sphere(this.r, n, k);
187   }
188}//Sphere