001/*
002 * Renderer 7. The MIT License.
003 * Copyright (c) 2022 rlkraft@pnw.edu
004 * See LICENSE for details.
005*/
006
007package renderer.pipeline;
008
009import renderer.scene.*;
010import renderer.scene.primitives.*;
011import static renderer.pipeline.PipelineLogger.*;
012
013import java.awt.Color;
014import java.util.List;
015import java.util.ArrayList;
016import java.util.Optional;
017
018/**
019   Clip in camera space any {@link LineSegment} that crosses the
020   camera's near clipping plane {@code z = -near}. Interpolate
021   {@link Vertex} color from any clipped off {@link Vertex} to
022   the new {@link Vertex}.
023<p>
024   This clipping algorithm is a simplification of the Liang-Barsky
025   Parametric Line Clipping algorithm.
026*/
027public final class NearClip_Line
028{
029   /**
030      If the {@link LineSegment} crosses the camera's near plane,
031      then return a clipped version that is contained in the far
032      side of the near plane. The new, clipped {@link LineSegment}
033      object is returned wrapped in an {@link Optional} object.
034      <p>
035      One new clipped {@link Vertex} object may be added to the
036      {@link Model}'s vertex list and one new interpolated
037      {@link Color} object may be added to the model's color list.
038      <p>
039      If the {@link LineSegment} is completely on the camera side
040      of the near plane, then return an empty {@link Optional} object
041      to indicate that the {@link LineSegment} should be discarded
042      from the model's {@link Primitive} list.
043
044      @param model   {@link Model} that the {@link LineSegment} {@code ls} comes from
045      @param ls      {@link LineSegment} to be clipped
046      @param camera  {@link Camera} that determines the near clipping plane
047      @return a clipped version of {@code ls} wrapped in an {@link Optional} object
048   */
049   public static Optional<Primitive> clip(final Model model,
050                                          final LineSegment ls,
051                                          final Camera camera)
052   {
053      // Make local copies of several values.
054      final double n = camera.n;
055
056      final int vIndex0 = ls.vIndexList.get(0);
057      final int vIndex1 = ls.vIndexList.get(1);
058      final Vertex v0 = model.vertexList.get(vIndex0);
059      final Vertex v1 = model.vertexList.get(vIndex1);
060
061      final double z0 = v0.z;
062      final double z1 = v1.z;
063
064      // 1. Check for trivial accept.
065      if ( z0 <= n && z1 <= n ) // on the far side of the near plane z = n
066      {
067         if (Clip.debug) logMessage("-- Near_Clip: Trivial accept.");
068         return Optional.of(ls); // better than "return ls;"
069      }
070      // 2. Check for trivial delete.
071      if ( z0 > n && z1 > n ) // on the near side of the near plane z = n
072      {
073         if (Clip.debug) logMessage("-- Near_Clip: Trivial delete.");
074         return Optional.empty(); // better than "return null;"
075      }
076      // 3. Need to clip this line segment.
077      else // crosses the near plane z = n
078      {
079         return Optional.of(interpolateNewVertex(model, ls, n));
080      }
081   }
082
083
084   /**
085      This method takes a line segment with one vertex on the near
086      side of the near clipping plane (in front of clipping plane)
087      and the other vertex on the far side of the near clipping plane
088      (behind the clipping plane).
089      <p>
090      This method returns the line segment from the clipping plane to the
091      vertex on the far side of the clipping plane.
092      <p>
093      This method solves for the value of {@code t} for which the z-component
094      of the parametric equation
095      <pre>{@code
096                  p(t) = (1 - t) * v0 + t * v1
097      }</pre>
098      intersects the given clipping plane, {@code z = n}. The solved for
099      value of {@code t} is then plugged into the x and y components of the
100      parametric equation to get the coordinates of the intersection point.
101
102      @param model  {@link Model} that the {@link LineSegment} {@code ls} comes from
103      @param ls     the {@link LineSegment} being clipped
104      @param n      the z-coordinate of the near clipping plane
105      @return the index of the new interpolated {@link Vertex} object
106   */
107   private static LineSegment interpolateNewVertex(final Model model,
108                                                   final LineSegment ls,
109                                                   final double n)
110   {
111      // Make local copies of several values.
112      final int vIndex0 = ls.vIndexList.get(0);
113      final Vertex v0  = model.vertexList.get(vIndex0);
114      final double v0x = v0.x;
115      final double v0y = v0.y;
116      final double v0z = v0.z;
117      final int cIndex0 = ls.cIndexList.get(0);
118      float c0[] = model.colorList.get(cIndex0).getRGBColorComponents(null);
119
120      final int vIndex1 = ls.vIndexList.get(1);
121      final Vertex v1  = model.vertexList.get(vIndex1);
122      final double v1x = v1.x;
123      final double v1y = v1.y;
124      final double v1z = v1.z;
125      final int cIndex1 = ls.cIndexList.get(1);
126      float c1[] = model.colorList.get(cIndex1).getRGBColorComponents(null);
127
128      // Interpolate between v1 and v0.
129      final double t = (n - v1z) / (v0z - v1z);
130
131      // Use the value of t to interpolate the coordinates of the new vertex.
132      final double x = (1 - t) * v1x + t * v0x;
133      final double y = (1 - t) * v1y + t * v0y;
134      final double z = n;
135
136      // Use the value of t to interpolate the color of the new vertex.
137      final float t_ = (float)t;
138      final float r = (1 - t_) * c1[0] + t_ * c0[0];
139      final float g = (1 - t_) * c1[1] + t_ * c0[1];
140      final float b = (1 - t_) * c1[2] + t_ * c0[2];
141
142      // Modify the Model to contain the new Vertex.
143      final Vertex newVertex = new Vertex(x, y, z);
144      final int vIndexNew = model.vertexList.size();
145      model.vertexList.add(newVertex);
146
147      // Modify the Model to contain the new Color.
148      final Color newColor = new Color(r, g, b);
149      final int cIndexNew = model.colorList.size();
150      model.colorList.add(newColor);
151
152      // Which Vertex of ls is on the near side of the clipping plane?
153      final int vNearIndex;
154      if ( v0z > n ) // clip off v0
155      {
156         vNearIndex = 0;
157      }
158      else // clip off v1
159      {
160         vNearIndex = 1;
161      }
162
163      if (Clip.debug)
164      {
165         final String vClipped = (0==vNearIndex) ? "v0" : "v1";
166         logMessage(String.format("-- Clip off %s at z=%.3f",
167                                        vClipped, n));
168         logMessage(String.format("-- t = %.25f", t));
169         logMessage(String.format("-- <x0, y0, z0> = <% .8f, % .8f, % .8f",
170                                       v0x, v0y, v0z));
171         logMessage(String.format("-- <x1, y1, z1> = <% .8f, % .8f, % .8f",
172                                       v1x, v1y, v1z));
173         logMessage(String.format("-- <x,  y,  z>  = <% .8f, % .8f, % .8f",
174                                       x,  y,  z));
175         logMessage(String.format("-- <r0, g0, b0> = <%.8f, %.8f, %.8f>",
176                                       c0[0], c0[1], c0[2]));
177         logMessage(String.format("-- <r1, g1, b1> = <%.8f, %.8f, %.8f>",
178                                       c1[0], c1[1], c1[2]));
179         logMessage(String.format("-- <r,  g,  b>  = <%.8f, %.8f, %.8f>",
180                                       r,  g,  b));
181      }
182
183      final LineSegment result;
184      if (0 == vNearIndex)
185      {
186         result = new LineSegment(vIndexNew, vIndex1, cIndexNew, cIndex1);
187      }
188      else
189      {
190         result = new LineSegment(vIndex0, vIndexNew, cIndex0, cIndexNew);
191      }
192      return result;
193   }
194
195
196
197   // Private default constructor to enforce noninstantiable class.
198   // See Item 4 in "Effective Java", 3rd Ed, Joshua Bloch.
199   private NearClip_Line() {
200      throw new AssertionError();
201   }
202}