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 frustum of a right circular cone
015   with its base in the xz-plane.
016<p>
017   See <a href="https://en.wikipedia.org/wiki/Frustum" target="_top">
018                https://en.wikipedia.org/wiki/Frustum</a>
019
020   @see Cone
021   @see ConeSector
022*/
023public class ConeFrustum extends Model implements MeshMaker
024{
025   public final double r1;
026   public final double r2;
027   public final double h;
028   public final int n;
029   public final int k;
030
031   /**
032      Create a frustum of a right circular cone with its base in the
033      xz-plane, a base radius of 1, top radius of 1/2, and height 1/2.
034   */
035   public ConeFrustum( )
036   {
037      this(1.0, 0.5, 0.5, 7, 16);
038   }
039
040
041   /**
042      Create a frustum of a right circular cone with its base in the
043      xz-plane, a base radius of {@code r}, top of the frustum at
044      height {@code h}, and with the cone's apex on the y-axis at
045      height {@code a}.
046   <p>
047      There must be at least three lines of longitude and at least
048      two circles of latitude.
049
050      @param n  number of circles of latitude
051      @param k  number of lines of longitude
052      @param r  radius of the base in the xz-plane
053      @param h  height of the frustum
054      @param a  height of the apex of the cone
055      @throws IllegalArgumentException if {@code n} is less than 2
056      @throws IllegalArgumentException if {@code k} is less than 3
057   */
058   public ConeFrustum(final int n, final int k,
059                      final double r, final double h, final double a)
060   {
061      this(r, (1 - h/a)*r, h, n, k);
062   }
063
064
065   /**
066      Create a frustum of a right circular cone with its base in the
067      xz-plane, a base radius of {@code r1}, top radius of {@code r2},
068      and height {@code h}.
069   <p>
070      This model works with either {@code r1 > r2} or {@code r1 < r2}.
071      In other words, the frustum can have its "apex" either above or
072      below the xz-plane.
073   <p>
074      There must be at least three lines of longitude and at least
075      two circles of latitude.
076
077      @param r1  radius of the base of the frustum
078      @param h   height of the frustum
079      @param r2  radius of the top of the frustum
080      @param n   number of circles of latitude
081      @param k   number of lines of longitude
082      @throws IllegalArgumentException if {@code n} is less than 2
083      @throws IllegalArgumentException if {@code k} is less than 3
084   */
085   public ConeFrustum(final double r1, final double h, final double r2,
086                      int n, int k)
087   {
088      super(String.format("Cone Frustum(%.2f,%.2f,%.2f,%d,%d)",
089                                        r1,  h,   r2,  n, k));
090
091      if (n < 2)
092         throw new IllegalArgumentException("n must be greater than 1");
093      if (k < 3)
094         throw new IllegalArgumentException("k must be greater than 2");
095
096      this.r1 = r1;
097      this.r2 = r2;
098      this.h = h;
099      this.n = n;
100      this.k = k;
101
102      // Create the frustum's geometry.
103
104      final double deltaH = h / (n - 1),
105                   deltaTheta = (2 * Math.PI) / k;
106
107      // An array of indexes to be used to create line segments.
108      final int[][] indexes = new int[n][k];
109
110      // Create all the vertices (from the top down).
111      int index = 0;
112      for (int j = 0; j < k; ++j) // choose an angle of longitude
113      {
114         final double c = Math.cos(j * deltaTheta),
115                      s = Math.sin(j * deltaTheta);
116         for (int i = 0; i < n; ++i) // choose a circle of latitude
117         {
118            final double slantRadius = (i/(n - 1.0)) * r1 + (1.0 - i/(n - 1.0)) * r2;
119            addVertex( new Vertex(slantRadius * c,
120                                  h - i * deltaH,
121                                  slantRadius * s) );
122            indexes[i][j] = index;
123            ++index;
124         }
125      }
126      addVertex( new Vertex(0, h, 0) );  // top center
127      final int topCenterIndex = index;
128      ++index;
129      addVertex( new Vertex(0, 0, 0) );  // bottom center
130      final int bottomCenterIndex = index;
131      ++index;
132
133      // Create all the horizontal circles of latitude around the frustum wall.
134      for (int i = 0; i < n; ++i)
135      {
136         for (int j = 0; j < k-1; ++j)
137         {
138            addPrimitive(new LineSegment(indexes[i][j], indexes[i][j+1]));
139         }
140         // close the circle
141         addPrimitive(new LineSegment(indexes[i][k-1], indexes[i][0]));
142      }
143
144      // Create the vertical half-trapazoids of longitude from north to south pole.
145      for (int j = 0; j < k; ++j)
146      {
147         // Create the triangle fan at the top.
148         addPrimitive(new LineSegment(topCenterIndex, indexes[0][j]));
149         // Create the slant lines from the top to the base.
150         addPrimitive(new LineSegment(indexes[0][j], indexes[n-1][j]));
151         // Create the triangle fan at the base.
152         addPrimitive(new LineSegment(indexes[n-1][j], bottomCenterIndex));
153      }
154   }
155
156
157
158   // Implement the MeshMaker interface (three methods).
159   @Override public int getHorzCount() {return n;}
160
161   @Override public int getVertCount() {return k;}
162
163   @Override
164   public ConeFrustum remake(final int n, final int k)
165   {
166      return new ConeFrustum(this.r1, this.h, this.r2,
167                             n, k);
168   }
169}//ConeFrustum