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 square pyramid
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 Pyramid
021*/
022public class PyramidFrustum extends Model implements MeshMaker
023{
024   public final double s1;
025   public final double s2;
026   public final double h;
027   public final int n;
028   public final int k;
029
030   /**
031      Create a frustum of a right square pyramid with its base in the
032      xz-plane, a base side length of 2, top side length of 1, and height 1/2.
033   */
034   public PyramidFrustum( )
035   {
036      this(2.0, 1.0, 0.5, 7, 4);
037   }
038
039
040   /**
041      Create a frustum of a right square pyramid with its base in the
042      xz-plane, a base side length of {@code s1}, top side length of
043      {@code s2}, and height {@code h}.
044   <p>
045      This model works with either {@code s1 > s2} or {@code s1 < s2}.
046      In other words, the frustum can have its "apex" either above or
047      below the xz-plane.
048
049      @param s1  side length of the base of the frustum
050      @param s2  side length of the top of the frustum
051      @param h   height of the frustum
052   */
053   public PyramidFrustum(final double s1, final double s2, final double h)
054   {
055      super(String.format("Pyramid Frustum(%.2f,%.2f,%.2f)", s1, s2, h));
056
057      this.s1 = s1;
058      this.s2 = s2;
059      this.h = h;
060      this.n = 1;
061      this.k = 1;
062
063      // Create the pyramid's geometry.
064      addVertex(new Vertex(-s1/2, 0, -s1/2),  // base
065                new Vertex(-s1/2, 0,  s1/2),
066                new Vertex( s1/2, 0,  s1/2),
067                new Vertex( s1/2, 0, -s1/2),
068                new Vertex(-s2/2, h, -s2/2),  // top
069                new Vertex(-s2/2, h,  s2/2),
070                new Vertex( s2/2, h,  s2/2),
071                new Vertex( s2/2, h, -s2/2));
072
073      // Create 6 faces.
074      addPrimitive(new LineSegment(0, 1), // base
075                   new LineSegment(1, 2),
076                   new LineSegment(2, 3),
077                   new LineSegment(3, 0),
078                   new LineSegment(0, 4), // 4 sides
079                   new LineSegment(1, 5),
080                   new LineSegment(2, 6),
081                   new LineSegment(3, 7),
082                   new LineSegment(4, 5), // top
083                   new LineSegment(5, 6),
084                   new LineSegment(6, 7),
085                   new LineSegment(7, 4));
086   }
087
088
089   /**
090      Create a frustum of a right square pyramid with its base in the
091      xz-plane, a base side length of {@code s}, top of the frustum at
092      height {@code h}, and with the pyramid's apex at on the y-axis at
093      height {@code a}.
094
095      @param n  number of lines of latitude
096      @param k  number of lines of longitude
097      @param s  side length of the base of the frustum
098      @param h  height of the frustum
099      @param a  height of the apex of the pyramid
100      @throws IllegalArgumentException if {@code n} is less than 0
101      @throws IllegalArgumentException if {@code k} is less than 1
102   */
103   public PyramidFrustum(final int n, final int k,
104                         final double s, final double h, final double a)
105   {
106      this(s, (1 - h/a)*s, h, n, k);
107   }
108
109
110   /**
111      Create a frustum of a right square pyramid with its base in the
112      xz-plane, a base side length of {@code s1}, top side length of
113      {@code s2}, and height {@code h}.
114   <p>
115      This model works with either {@code s1 > s2} or {@code s1 < s2}.
116      In other words, the frustum can have its "apex" either above or
117      below the xz-plane.
118
119      @param s1  side length of the base of the frustum
120      @param s2  side length of the top of the frustum
121      @param h   height of the frustum
122      @param n   number of lines of latitude
123      @param k   number of lines of longitude
124      @throws IllegalArgumentException if {@code n} is less than 0
125      @throws IllegalArgumentException if {@code k} is less than 1
126   */
127   public PyramidFrustum(double s1, double s2, double h,
128                         final int n, final int k)
129   {
130      super(String.format("Pyramid Frustum(%.2f,%.2f,%.2f,%d,%d)",
131                                           s1,  s2,  h,   n, k));
132
133      if (n < 0)
134         throw new IllegalArgumentException("n must be greater than or equal to 0");
135      if (k < 1)
136         throw new IllegalArgumentException("k must be greater than 0");
137
138      this.s1 = s1;
139      this.s2 = s2;
140      this.h = h;
141      this.n = n;
142      this.k = k;
143
144      // Create the frustum's geometry.
145      int index = 0;
146
147      // Create all the lines of longitude from the top, down to the base,
148      // across the base, then back up to the top, and across the top.
149      s1 = s1/2;
150      s2 = s2/2;
151      final double delta1 = (2 * s1) / k,
152                   delta2 = (2 * s2) / k;
153      // lines of "longitude" perpendicular to the x-axis
154      for (int j = 0; j <= k; ++j)
155      {
156         final double d1 = j * delta1,
157                      d2 = j * delta2;
158         addVertex(new Vertex(-s2+d2, h, -s2),
159                   new Vertex(-s1+d1, 0, -s1),
160                   new Vertex(-s1+d1, 0,  s1),
161                   new Vertex(-s2+d2, h,  s2));
162         addPrimitive(new LineSegment(index+0, index+1),
163                      new LineSegment(index+1, index+2),
164                      new LineSegment(index+2, index+3),
165                      new LineSegment(index+3, index+0));
166         index += 4;
167      }
168      // lines of "longitude" perpendicular to the z-axis
169      for (int j = 0; j <= k; ++j)
170      {
171         final double d1 = j * delta1,
172                      d2 = j * delta2;
173         addVertex(new Vertex( s2, h, -s2+d2),
174                   new Vertex( s1, 0, -s1+d1),
175                   new Vertex(-s1, 0, -s1+d1),
176                   new Vertex(-s2, h, -s2+d2));
177         addPrimitive(new LineSegment(index+0, index+1),
178                      new LineSegment(index+1, index+2),
179                      new LineSegment(index+2, index+3),
180                      new LineSegment(index+3, index+0));
181         index += 4;
182      }
183      // Create all the lines of "latitude" around the pyramid, starting
184      // from the base and working up to the top.
185      final double deltaH = h / (n + 1),
186                   deltaS = (s1 - s2) / (n + 1);
187      double s = s1;
188      for (int i = 0; i <= n; ++i)
189      {
190         h = i * deltaH;
191         addVertex(new Vertex( s, h,  s),
192                   new Vertex( s, h, -s),
193                   new Vertex(-s, h, -s),
194                   new Vertex(-s, h,  s));
195         addPrimitive(new LineSegment(index+0, index+1),
196                      new LineSegment(index+1, index+2),
197                      new LineSegment(index+2, index+3),
198                      new LineSegment(index+3, index+0));
199         s -= deltaS;
200         index += 4;
201      }
202   }
203
204
205
206   // Implement the MeshMaker interface (three methods).
207   @Override public int getHorzCount() {return n;}
208
209   @Override public int getVertCount() {return k;}
210
211   @Override
212   public PyramidFrustum remake(final int n, final int k)
213   {
214      return new PyramidFrustum(this.s1, this.s2,
215                                this.h,
216                                n, k);
217   }
218}//PyramidFrustum