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 right circular cone with its
015   base parallel to the xz-plane and its apex on the positive y-axis.
016<p>
017   By a partial cone we mean a cone over a circular sector of the
018   cone's base and also cutting off the top part of the cone (the
019   part between the apex and a circle of latitude) leaving a frustum
020   of the (partial) cone.
021
022   @see Cone
023   @see ConeFrustum
024   @see CircleSector
025   @see DiskSector
026   @see RingSector
027   @see CylinderSector
028   @see SphereSector
029   @see TorusSector
030*/
031public class ConeSector extends Model implements MeshMaker
032{
033   public final double r;
034   public final double h;
035   public final double t;
036   public final double theta1;
037   public final double theta2;
038   public final int n;
039   public final int k;
040
041   /**
042      Create half of a right circular cone with its base in the xz-plane,
043      a base radius of 1, height 1, and apex on the positive y-axis.
044   */
045   public ConeSector( )
046   {
047      this(1, 1, Math.PI/2, 3*Math.PI/2, 15, 8);
048   }
049
050
051   /**
052      Create a part of the cone with its base in the xz-plane,
053      a base radius of {@code r}, height {@code h}, and apex
054      on the y-axis.
055   <p>
056      The partial cone is a cone over the circular sector
057      from angle {@code theta1} to angle {@code theta2} (in the
058      counterclockwise direction). In other words, the (partial)
059      circles of latitude in the model extend from angle
060      {@code theta1} to angle {@code theta2} (in the
061      counterclockwise direction).
062   <p>
063      The last two parameters determine the number of lines of longitude
064      and the number of (partial) circles of latitude in the model.
065   <p>
066      If there are {@code n} circles of latitude in the model (including
067      the bottom edge), then each line of longitude will have {@code n}
068      line segments. If there are {@code k} lines of longitude, then each
069      (partial) circle of latitude will have {@code k-1} line segments.
070   <p>
071      There must be at least four lines of longitude and at least
072      one circle of latitude.
073
074      @param r       radius of the base in the xz-plane
075      @param h       height of the apex on the y-axis
076      @param theta1  beginning longitude angle of the sector (in radians)
077      @param theta2  ending longitude angle of the sector (in radians)
078      @param n       number of circles of latitude around the cone
079      @param k       number of lines of longitude
080      @throws IllegalArgumentException if {@code n} is less than 2
081      @throws IllegalArgumentException if {@code k} is less than 4
082   */
083   public ConeSector(final double r,
084                     final double h,
085                     final double theta1, final double theta2,
086                     final int n, final int k)
087   {
088      this(r, h, h, theta1, theta2, n+1, k);
089   }
090
091
092   /**
093      Create a part of the cone with its base in the xz-plane,
094      a base radius of {@code r}, height {@code  h}, and apex
095      on the y-axis.
096   <p>
097      If {@code 0 < t < h}, then the partial cone is a frustum
098      with its base in the xz-plane and the top of the frustum at
099      {@code y = t}.
100   <p>
101      The partial cone is a cone over the circular sector
102      from angle {@code theta1} to angle {@code theta2} (in the
103      counterclockwise direction). In other words, the (partial)
104      circles of latitude in the model extend from angle
105      {@code theta1} to angle {@code theta2} (in the
106      counterclockwise direction).
107   <p>
108      The last two parameters determine the number of lines of longitude
109      (not counting one edge of any removed sector) and the number of
110      (partial) circles of latitude (not counting the top edge of the
111      frustum) in the model.
112   <p>
113      If there are {@code n} circles of latitude in the model (including
114      the bottom edge but not the top edge of the frustum), then each
115      line of longitude will have {@code n+1} line segments. If there are
116      {@code k} lines of longitude (not counting one edge of any removed
117      sector), then each (partial) circle of latitude will have {@code k}
118      line segments.
119   <p>
120      There must be at least four lines of longitude and at least
121      two circles of latitude.
122
123      @param r       radius of the base in the xz-plane
124      @param h       height of the apex on the y-axis
125      @param t       top of the frustum of the come
126      @param theta1  beginning longitude angle of the sector (in radians)
127      @param theta2  ending longitude angle of the sector (in radians)
128      @param n       number of circles of latitude around the cone
129      @param k       number of lines of longitude
130      @throws IllegalArgumentException if {@code n} is less than 2
131      @throws IllegalArgumentException if {@code k} is less than 4
132      @throws IllegalArgumentException if {@code h} is less than {@code t}
133   */
134   public ConeSector(final double r,
135                     final double h,
136                     final double t,
137                     double theta1, double theta2,
138                     final int n, final int k)
139   {
140      super(String.format("Cone Sector(%.2f,%.2f,%.2f,%.2f,%.2f,%d,%d)",
141                                       r, h, t, theta1, theta2, n, k));
142
143      if (n < 2)
144         throw new IllegalArgumentException("n must be greater than 1");
145      if (k < 4)
146         throw new IllegalArgumentException("k must be greater than 3");
147      if (h < t)
148         throw new IllegalArgumentException("h must be greater than or equal to t");
149
150      theta1 = theta1 % (2*Math.PI);
151      theta2 = theta2 % (2*Math.PI);
152      if (theta1 < 0) theta1 = 2*Math.PI + theta1;
153      if (theta2 < 0) theta2 = 2*Math.PI + theta2;
154      if (theta2 <= theta1) theta2 = theta2 + 2*Math.PI;
155
156      this.r = r;
157      this.h = h;
158      this.t = t;
159      this.theta1 = theta1;
160      this.theta2 = theta2;
161      this.n = n;
162      this.k = k;
163
164      // Create the cone's geometry.
165
166      final double deltaH = t / (n - 1),
167                   deltaTheta = (theta2 - theta1) / (k - 1);
168
169      // An array of indexes to be used to create line segments.
170      final int[][] indexes = new int[n][k];
171
172      // Create all the vertices (from the bottom up).
173      int index = 0;
174      for (int j = 0; j < k; ++j) // choose an angle of longitude
175      {
176         final double c = Math.cos(theta1 + j * deltaTheta),
177                      s = Math.sin(theta1 + j * deltaTheta);
178         for (int i = 0; i < n; ++i) // choose a circle of latitude
179         {
180            final double slantRadius = r * (1 - i * deltaH / h);
181            addVertex( new Vertex(slantRadius * c,
182                                  i * deltaH,
183                                  slantRadius * s) );
184            indexes[i][j] = index++;
185         }
186      }
187      addVertex( new Vertex(0, 0, 0) ); // bottom center
188      final int bottomCenterIndex = index;
189      ++index;
190
191      // Create all the horizontal (partial) circles of latitude around the cone.
192      for (int i = 0; i < n; ++i)
193      {
194         for (int j = 0; j < k - 1; ++j)
195         {
196            addPrimitive(new LineSegment(indexes[i][j], indexes[i][j+1]));
197         }
198      }
199
200      // Create the slanted lines of longitude from the base to the
201      // top circle of latitude, and the triangle fan in the base.
202      for (int j = 0; j < k; ++j)
203      {
204         addPrimitive(new LineSegment(bottomCenterIndex, indexes[0][j]));
205
206         for (int i = 0; i < n - 1; ++i)
207         {
208            addPrimitive(new LineSegment(indexes[i][j], indexes[i+1][j]));
209         }
210      }
211   }
212
213
214
215   // Implement the MeshMaker interface (three methods).
216   @Override public int getHorzCount() {return n;}
217
218   @Override public int getVertCount() {return k;}
219
220   @Override
221   public ConeSector remake(final int n, final int k)
222   {
223      return new ConeSector(this.r,
224                            this.h,
225                            this.t,
226                            this.theta1, this.theta2,
227                            n, k);
228   }
229}//ConeSector