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
013import java.util.function.DoubleBinaryOperator;
014import java.util.function.ToDoubleBiFunction; // could use this instead
015//https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html
016
017/**
018   Create a wireframe model of a parametric surface in space.
019<p>
020   See <a href="https://en.wikipedia.org/wiki/Parametric_surface" target="_top">
021                https://en.wikipedia.org/wiki/Parametric_surface</a>
022
023   @see ParametricCurve
024*/
025public class ParametricSurface extends Model implements MeshMaker
026{
027   public final DoubleBinaryOperator x;
028   public final DoubleBinaryOperator y;
029   public final DoubleBinaryOperator z;
030   public final double s1;
031   public final double s2;
032   public final double t1;
033   public final double t2;
034   public final int n;
035   public final int k;
036
037   /**
038      Create a graph of the function with the following formula,
039      <pre>{@code
040          f(x,z) = sin(PI*x) * sin(PI*z)
041      }</pre>
042      as a parametric surface.
043   */
044   public ParametricSurface()
045   {
046      this((s,t) -> Math.sin(Math.PI*s) * Math.sin(Math.PI*t),
047           -1.0, 1.0, -1.0, 1.0,
048            49, 49);
049   }
050
051
052   /**
053      Create a graph of a function of two variables
054      {@code y = f(x, z)} as a parametric surface with
055      the given parameter ranges in the {@code x} and
056      {@code z} directions.
057
058      @param f   function of x and z
059      @param x1  beginning value of x-parameter range
060      @param x2  ending value of x-parameter range
061      @param z1  beginning value of y-parameter range
062      @param z2  ending value of z-parameter range
063      @param n   number of mesh lines in x-range
064      @param k   number of mesh lines in y-range
065      @throws IllegalArgumentException if {@code n} is less than 2
066      @throws IllegalArgumentException if {@code k} is less than 2
067   */
068   public ParametricSurface(final DoubleBinaryOperator f,
069                            final double x1, final double x2,
070                            final double z1, final double z2,
071                            final int n, final int k)
072   {
073      this((x,z) -> x, f, (x,z) -> z, x1, x2, z1, z2, n, k);
074   }
075
076
077   /**
078      Create a parametric surface in space,
079      <pre>{@code
080         x = x(s,t)
081         y = y(s,t)
082         z = z(s,t)
083      }</pre>
084      with the parameters {@code s} and {@code t} having
085      the given parameter ranges and the given number of
086      mesh lines in each parametric direction.
087
088      @param x   component function in the x-direction
089      @param y   component function in the y-direction
090      @param z   component function in the z-direction
091      @param s1  beginning value of first parameter range
092      @param s2  ending value of first parameter range
093      @param t1  beginning value of second parameter range
094      @param t2  ending value of second parameter range
095      @param n   number of mesh lines in first range
096      @param k   number of mesh lines in second range
097      @throws IllegalArgumentException if {@code n} is less than 2
098      @throws IllegalArgumentException if {@code k} is less than 2
099   */
100   public ParametricSurface(final DoubleBinaryOperator x,
101                            final DoubleBinaryOperator y,
102                            final DoubleBinaryOperator z,
103                            final double s1, final double s2,
104                            final double t1, final double t2,
105                            final int n, final int k)
106   {
107      this(x, y, z, s1, s2, t1, t2, n, k,
108           String.format("Parametric Surface(%d,%d)", n, k));
109   }
110
111
112   /**
113      Create a parametric surface in space,
114      <pre>{@code
115         x = x(s,t)
116         y = y(s,t)
117         z = z(s,t)
118      }</pre>
119      with the parameters {@code s} and {@code t} having
120      the given parameter ranges and the given number of
121      mesh lines in each parametric direction.
122
123      @param x     component function in the x-direction
124      @param y     component function in the y-direction
125      @param z     component function in the z-direction
126      @param s1    beginning value of first parameter range
127      @param s2    ending value of first parameter range
128      @param t1    beginning value of second parameter range
129      @param t2    ending value of second parameter range
130      @param n     number of mesh lines in first range
131      @param k     number of mesh lines in second range
132      @param name  {@link String} name for this surface
133      @throws IllegalArgumentException if {@code n} is less than 2
134      @throws IllegalArgumentException if {@code k} is less than 2
135   */
136   public ParametricSurface(final DoubleBinaryOperator x,
137                            final DoubleBinaryOperator y,
138                            final DoubleBinaryOperator z,
139                            final double s1, final double s2,
140                            final double t1, final double t2,
141                            final int n, final int k,
142                            String name)
143   {
144      super(name);
145
146      if (n < 2)
147         throw new IllegalArgumentException("n must be greater than 1");
148      if (k < 2)
149         throw new IllegalArgumentException("k must be greater than 1");
150
151      this.x = x;
152      this.y = y;
153      this.z = z;
154      this.s1 = s1;
155      this.s2 = s2;
156      this.t1 = t1;
157      this.t2 = t2;
158      this.n = n;
159      this.k = k;
160
161      // Create the surface's geometry.
162
163      final double deltaS = (s2 - s1) / (n - 1), // lines of latitude (dy)
164                   deltaT = (t2 - t1) / (k - 1); // lines of longitude (dx)
165
166      // An array of vertices to be used to create the line segments.
167      final Vertex[][] v = new Vertex[n][k];
168
169      // Create all the vertices.
170      for (int i = 0; i < n; ++i) // choose a line of latitude
171      {
172         for (int j = 0; j < k; ++j) // choose a line of longitude
173         {
174            v[i][j] = new Vertex(x.applyAsDouble(s1 + i*deltaS, t1 + j*deltaT),
175                                 y.applyAsDouble(s1 + i*deltaS, t1 + j*deltaT),
176                                 z.applyAsDouble(s1 + i*deltaS, t1 + j*deltaT));
177         }
178      }
179
180      // Add all of the vertices to this model.
181      for (int i = 0; i < n; ++i)
182      {
183         for (int j = 0; j < k; ++j)
184         {
185            addVertex( v[i][j] );
186         }
187      }
188
189      // Create the horizontal line segments.
190      for (int i = 0; i < n; ++i) // choose a line of latitude
191      {
192         for (int j = 0; j < k - 1; ++j) // choose a line of longitude
193         {  //                               v[i][j]        v[i][j+1]
194            addPrimitive(new LineSegment( (i * k) + j, (i * k) + (j+1) ));
195         }
196      }
197
198      // Create the vertical line segments.
199      for (int j = 0; j < k; ++j) // choose a line of longitude
200      {
201         for (int i = 0; i < n - 1; ++i) // choose a line of latitude
202         {  //                              v[i][j]         v[i+1][j]
203            addPrimitive(new LineSegment( (i * k) + j, ((i+1) * k) + j ));
204         }
205      }
206   }
207
208
209
210   // Implement the MeshMaker interface (three methods).
211   @Override public int getHorzCount() {return n;}
212
213   @Override public int getVertCount() {return k;}
214
215   @Override
216   public ParametricSurface remake(final int n, final int k)
217   {
218      return new ParametricSurface(this.x, this.y, this.z,
219                                   this.s1, this.s2,
220                                   this.t1, this.t2,
221                                   n, k);
222   }
223}//ParametricSurface