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}