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 tetrahedron as a
015   triangular pyramid with an equilateral triangle
016   base (centered at the origin in the xz-plane)
017   whose three vertices are connected to a 4th vertex
018   on the positive y-axis.
019
020   @see Tetrahedron
021*/
022public class TriangularPyramid extends Model implements MeshMaker
023{
024   public final double r;
025   public final double h;
026   public final int n;
027   public final int k;
028
029   /**
030      Create a regular tetrahedron having side length
031      {@code sqrt(3)/sqrt(2)}, with one face in the
032      xz-plane with its center at the origin, and the
033      4th vertex on the positive y-axis at height 1.
034   */
035   public TriangularPyramid( )
036   {
037      this(Math.sqrt(3)/Math.sqrt(2)); // makes the height = 1
038      //or
039      //this(Math.sqrt(3));  // make the height = sqrt(2) > 1
040   }
041
042
043   /**
044      Create a regular tetrahedron having side length {@code s},
045      with one face in the xz-plane with its center at the origin,
046      and with the 4th vertex on the positive y-axis at
047      height {@code s*sqrt(2)/sqrt(3)}.
048
049      @param s  the length of the regular tetrahedron's sides
050   */
051   public TriangularPyramid(final double s)
052   {
053      this(s/Math.sqrt(3), s*Math.sqrt(2)/Math.sqrt(3));
054   }
055
056
057   /**
058      Create a tetrahedron with one face being an equilateral triangle
059      inscribed in a circle of radius {@code r} centered at the origin
060      of the xz-plane and with the 4th vertex on the y-axis at height
061      {@code h}.
062   <p>
063      If {@code h = r * sqrt(2)}, then the tetrahedron is a regular tetrahedron.
064      with side length {@code s = r * sqrt(3)}.
065   <p>
066      Another way to state this is, if an equilateral triangle is inscribed
067      in a circle of radius {@code r}, then the edge length of the triangle
068      is {@code r*sqrt(3)} and the height of the regular tetrahedron made
069      from the triangle is {@code r*sqrt(2)}.
070
071      @param r  radius of circle in xz-plane that the equilateral base is inscribed in
072      @param h  coordinate on the y-axis of the apex
073   */
074   public TriangularPyramid(final double r, final double h)
075   {
076      super(String.format("Triangular_Pyramid(%.2f,%.2f)", r, h));
077
078      this.r = r;
079      this.h = h;
080      this.n = 1;
081      this.k = 1;
082
083      // Create the tetrahedron's geometry.
084      final double sqrt3 = Math.sqrt(3.0);
085      addVertex(new Vertex( r,   0,    0),  // three vertices around the bottom face
086                new Vertex(-r/2, 0,  r*0.5*sqrt3),
087                new Vertex(-r/2, 0, -r*0.5*sqrt3),
088                new Vertex( 0,   h,    0)); // vertex at the top
089
090      // Create 6 line segments for 3 faces.
091      addPrimitive(new LineSegment(0, 1),   // bottom face
092                     new LineSegment(1, 2),
093                     new LineSegment(2, 0),
094                     new LineSegment(0, 3),   // edge 1
095                     new LineSegment(1, 3),   // edge 2
096                     new LineSegment(2, 3));  // edge 3
097   }
098
099
100   /**
101      Create a tetrahedron with one face being an equilateral triangle
102      inscribed in a circle of radius {@code r} centered at the origin
103      of the xz-plane and with the 4th vertex on the y-axis at height
104      {@code h}.
105   <p>
106      If {@code h = r * sqrt(2)}, then the tetrahedron is a regular tetrahedron.
107      with side length {@code s = r * sqrt(3)}.
108   <p>
109      Another way to state this is, if an equilateral triangle is inscribed
110      in a circle of radius {@code r}, then the edge length of the triangle
111      is {@code r*sqrt(3)} and the height of the regular tetrahedron made
112      from the triangle is {@code r*sqrt(2)}.
113
114      @param r  radius of circle in xz-plane that the equilateral base is inscribed in
115      @param h  coordinate on the y-axis of the apex
116      @param n  number of lines of latitude around the body of the pyramid
117      @param k  number of triangles in the triangle fan at the top of each side
118      @throws IllegalArgumentException if {@code n} is less than 1
119      @throws IllegalArgumentException if {@code k} is less than 1
120   */
121   public TriangularPyramid(final double r, final double h,
122                            final int n, final int k)
123   {
124      super(String.format("Triangular_Pyramid(%.2f,%.2f,%d,%d)",
125                                              r,   h,   n, k));
126
127      if (n < 1)
128         throw new IllegalArgumentException("n must be greater than 0");
129      if (k < 1)
130         throw new IllegalArgumentException("k must be greater than 0");
131
132      this.r = r;
133      this.h = h;
134      this.n = n;
135      this.k = k;
136
137      // Create the pyramid's geometry.
138      final Vertex apex = new Vertex(0, h, 0),
139           centerVertex = new Vertex(0, 0, 0);
140      addVertex(apex,
141                centerVertex);
142      final int apexIndex = 0,
143                centerIndex = 1;
144      int index = 2;
145
146      // Create all the lines of "longitude" from the apex, down
147      // to the base, and then to the center of the base.
148      final double sqrt3 = Math.sqrt(3.0);
149      // Three vertices around the bottom face.
150      final Vertex v0 = new Vertex( r,   0,    0),
151                   v1 = new Vertex(-r/2, 0,  r*0.5*sqrt3),
152                   v2 = new Vertex(-r/2, 0, -r*0.5*sqrt3);
153      for (int j = 0; j < k; ++j)
154      {
155         final double t = j * (1.0 / k);
156         // use linear interpolation (lerp)
157         addVertex( new Vertex(
158         //         (1-t)*v0  +  t*v1
159                    (1-t)*v0.x + t*v1.x,
160                    (1-t)*v0.y + t*v1.y,
161                    (1-t)*v0.z + t*v1.z ));
162         addVertex( new Vertex(
163         //         (1-t)*v1  +  t*v2
164                    (1-t)*v1.x + t*v2.x,
165                    (1-t)*v1.y + t*v2.y,
166                    (1-t)*v1.z + t*v2.z ));
167         addVertex( new Vertex(
168         //         (1-t)*v2  +  t*v0
169                    (1-t)*v2.x + t*v0.x,
170                    (1-t)*v2.y + t*v0.y,
171                    (1-t)*v2.z + t*v0.z ));
172
173         // first side
174         addPrimitive(new LineSegment(apexIndex, index+0),
175                      new LineSegment(index+0, centerIndex));
176         // second side
177         addPrimitive(new LineSegment(apexIndex, index+1),
178                      new LineSegment(index+1, centerIndex));
179         // third side
180         addPrimitive(new LineSegment(apexIndex, index+2),
181                      new LineSegment(index+2, centerIndex));
182
183         index += 3;
184      }
185      // Create all the lines of "latitude" around the pyramid, starting
186      // from the base and working upwards.
187      for (int i = 0; i < n; ++i)
188      {
189         final double t = i * (1.0 / n);
190         // Use linear interpolation (lerp).
191         addVertex( new Vertex(
192         //         (1-t)*v0   + t*apex
193                    (1-t)*v0.x + t*apex.x,
194                    (1-t)*v0.y + t*apex.y,
195                    (1-t)*v0.z + t*apex.z ));
196         addVertex( new Vertex(
197         //         (1-t)*v1   + t*apex
198                    (1-t)*v1.x + t*apex.x,
199                    (1-t)*v1.y + t*apex.y,
200                    (1-t)*v1.z + t*apex.z ));
201         addVertex( new Vertex(
202         //         (1-t)*v2   + t*apex
203                    (1-t)*v2.x + t*apex.x,
204                    (1-t)*v2.y + t*apex.y,
205                    (1-t)*v2.z + t*apex.z ));
206
207         addPrimitive(new LineSegment(index+0, index+1),
208                      new LineSegment(index+1, index+2),
209                      new LineSegment(index+2, index+0));
210
211         index += 3;
212      }
213   }
214
215
216
217   // Implement the MeshMaker interface (three methods).
218   @Override public int getHorzCount() {return n;}
219
220   @Override public int getVertCount() {return k;}
221
222   @Override
223   public TriangularPyramid remake(final int n, final int k)
224   {
225      return new TriangularPyramid(this.r, this.h,
226                                   n, k);
227   }
228}//TriangularPyramid