001/** 002 * SurfaceUtils -- Utility methods for working with NURBS surfaces. 003 * 004 * Copyright (C) 2009-2025, by Joseph A. Huwaldt. All rights reserved. 005 * 006 * This library is free software; you can redistribute it and/or modify it under the terms 007 * of the GNU Lesser General Public License as published by the Free Software Foundation; 008 * either version 2 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, but WITHOUT ANY 011 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 012 * PARTICULAR PURPOSE. See the GNU Library General Public License for more details. 013 * 014 * You should have received a copy of the GNU Lesser General Public License along with 015 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - 016 * Suite 330, Boston, MA 02111-1307, USA. Or visit: http://www.gnu.org/licenses/lgpl.html 017 * 018 * This source file is based on, but heavily modified from, the LGPL jgeom (Geometry 019 * Library for Java) code by Samuel Gerber, Copyright (C) 2005. 020 */ 021package geomss.geom.nurbs; 022 023import geomss.geom.GeomList; 024import java.util.List; 025import static java.util.Objects.requireNonNull; 026import javolution.context.StackContext; 027import static javolution.lang.MathLib.*; 028import javolution.util.FastTable; 029 030/** 031 * A collection of utility methods for working with NURBS surfaces. 032 * 033 * <p> Modified by: Joseph A. Huwaldt </p> 034 * 035 * @author Joseph A. Huwaldt, Date: September 10, 2010 036 * @version February 17, 2025 037 */ 038public final class SurfaceUtils { 039 040 /** 041 * The resource bundle for this package. 042 */ 043 //private static final ResourceBundle RESOURCES = geomss.geom.AbstractGeomElement.RESOURCES; 044 045 /** 046 * Elevate the degree of a surface to the specified degree in the S & T directions. If 047 * the specified degrees are both ≤ the current degree of the surface in each 048 * direction, then no change is made and a reference to the input surface is returned. 049 * 050 * @param srf The surface to have the degrees elevated. May not be null. 051 * @param degreeS The desired degree for the surface to be elevated to in the 052 * S-direction. 053 * @param degreeT The desired degree for the surface to be elevated to in the 054 * T-direction. 055 * @return The input NurbsSurface with the S & T degree elevated to the specified 056 * degrees. 057 */ 058 public static NurbsSurface elevateToDegree(NurbsSurface srf, int degreeS, int degreeT) { 059 int pS = srf.getSDegree(); 060 int pT = srf.getTDegree(); 061 if (degreeS <= pS && degreeT <= pT) 062 return srf; 063 064 int tS = degreeS - pS; 065 int tT = degreeT - pT; 066 return degreeElevate(srf, tS, tT); 067 } 068 069 /** 070 * Elevate the degree of a surface by the specified number of times in the S and T 071 * directions. 072 * 073 * @param srf The surface to have the degrees elevated. May not be null. 074 * @param tS The number of times to elevate the degree in the S-direction. 075 * @param tT The number of times to elevate the degree in the T-direction. 076 * @return The input NurbsSurface with the S & T degree elevated by the specified 077 * amount. 078 */ 079 public static NurbsSurface degreeElevate(NurbsSurface srf, int tS, int tT) { 080 srf = degreeElevateS(requireNonNull(srf), tS); 081 srf = degreeElevateT(srf, tT); 082 return srf; 083 } 084 085 /** 086 * Elevate the degree of a surface by the specified number of times in the 087 * S-direction. 088 * 089 * @param srf The surface to have the S-degree elevated. 090 * @param t The number of times to elevate the degree in the S-direction. 091 * @return The input NurbsSurface with the S-degree elevated by the specified amount. 092 */ 093 private static NurbsSurface degreeElevateS(NurbsSurface srf, int t) { 094 if (t <= 0) 095 return srf; 096 097 StackContext.enter(); 098 try { 099 KnotVector kv = null; 100 FastTable<List<ControlPoint>> cpNetList = FastTable.newInstance(); 101 102 // Degree elevate each defining column curve. 103 int numCols = srf.getNumberOfColumns(); 104 for (int i = 0; i < numCols; ++i) { 105 BasicNurbsCurve c1 = srf.getSCurve(i); 106 NurbsCurve c2 = CurveUtils.degreeElevate(c1, t); 107 kv = c2.getKnotVector(); 108 cpNetList.add(c2.getControlPoints()); 109 } 110 111 // Create a new surface from the new knot vector and set of control points. 112 ControlPointNet cpNet = ControlPointNet.valueOf(cpNetList); 113 BasicNurbsSurface newSrf = BasicNurbsSurface.newInstance(cpNet, kv, srf.getTKnotVector()); 114 return StackContext.outerCopy(newSrf); 115 116 } finally { 117 StackContext.exit(); 118 } 119 } 120 121 /** 122 * Elevate the degree of a surface by the specified number of times in the 123 * T-direction. 124 * 125 * @param srf The surface to have the T-degree elevated. 126 * @param t The number of times to elevate the degree in the T-direction. 127 * @return The input NurbsSurface with the T-degree elevated by the specified amount. 128 */ 129 private static NurbsSurface degreeElevateT(NurbsSurface srf, int t) { 130 if (t <= 0) 131 return srf; 132 133 StackContext.enter(); 134 try { 135 KnotVector kv = null; 136 FastTable<List<ControlPoint>> cpNetList = FastTable.newInstance(); 137 138 // Degree elevate each defining row curve. 139 int numRows = srf.getNumberOfRows(); 140 for (int j = 0; j < numRows; ++j) { 141 BasicNurbsCurve c1 = srf.getTCurve(j); 142 NurbsCurve c2 = CurveUtils.degreeElevate(c1, t); 143 kv = c2.getKnotVector(); 144 cpNetList.add(c2.getControlPoints()); 145 } 146 147 // Create a new surface from the new knot vector and set of control points. 148 ControlPointNet cpNet = ControlPointNet.valueOf(cpNetList).transpose(); 149 BasicNurbsSurface newSrf = BasicNurbsSurface.newInstance(cpNet, srf.getSKnotVector(), kv); 150 return StackContext.outerCopy(newSrf); 151 152 } finally { 153 StackContext.exit(); 154 } 155 } 156 157 /** 158 * Decompose a NURBS surface into a list of lists of Bezier patches. The returned list 159 * contains lists of NURBS surfaces that represent each Bezier patch. Each list 160 * contains patches in the s-direction (t-direction is across the lists). 161 * 162 * @param srf The surface to be decomposed into Bezier patches. May not be null. 163 * @return A list of lists of Bezier patches that have a degree of at least 2. 164 */ 165 public static GeomList<GeomList<BasicNurbsSurface>> decomposeToBezier(NurbsSurface srf) { 166 int numCrvRows = srf.getNumberOfRows(); 167 168 // Create knot vectors for the Bezier patches. 169 KnotVector bezierKVs = CurveUtils.bezierKnotVector(max(srf.getSDegree(), 2)); 170 KnotVector bezierKVt = CurveUtils.bezierKnotVector(max(srf.getTDegree(), 2)); 171 172 // First, loop over the rows of the control point network and extract Bezier segments in the T-direction. 173 FastTable<List<? extends List<ControlPoint>>> strips = FastTable.newInstance(); 174 ControlPointNet cpNet = srf.getControlPoints(); 175 for (int rowIdx = 0; rowIdx < numCrvRows; ++rowIdx) { 176 List<ControlPoint> cps = cpNet.getRow(rowIdx); 177 BasicNurbsCurve crv = BasicNurbsCurve.newInstance(cps, srf.getTKnotVector()); 178 GeomList<BasicNurbsCurve> segments = CurveUtils.decomposeToBezier(crv); 179 180 // Turn curve segments into a list of control point lists. 181 FastTable<List<ControlPoint>> cpSegments = FastTable.newInstance(); 182 int nSegs = segments.size(); 183 for (int i = 0; i < nSegs; ++i) { 184 BasicNurbsCurve seg = segments.get(i); 185 cpSegments.add(seg.getControlPoints()); 186 } 187 188 strips.add(cpSegments); 189 } 190 191 int numPatchCols = strips.get(0).size(); 192 int numColsPerPatch = strips.get(0).get(0).size(); 193 194 // Create the output list of lists. 195 GeomList<GeomList<BasicNurbsSurface>> patches = GeomList.newInstance(); 196 for (int i = 0; i < numPatchCols; ++i) { 197 patches.add(GeomList.newInstance()); 198 } 199 200 StackContext.enter(); 201 try { 202 // Loop over the all the columns of Bezier patches in this surface. 203 FastTable<ControlPoint> cps = FastTable.newInstance(); 204 for (int patchCol = 0; patchCol < numPatchCols; ++patchCol) { 205 206 // Loop over all the columns of control points in this Bezier patch column. 207 FastTable<List<? extends List<ControlPoint>>> patchSegmentLists = FastTable.newInstance(); 208 for (int colIdx = 0; colIdx < numColsPerPatch; ++colIdx) { 209 // Create a curve in s-direction from a column of control points from the Bezier segments in this patch column. 210 for (int rowIdx = 0; rowIdx < numCrvRows; ++rowIdx) { 211 cps.add(strips.get(rowIdx).get(patchCol).get(colIdx)); 212 } 213 BasicNurbsCurve crv = BasicNurbsCurve.newInstance(cps, srf.getSKnotVector()); 214 215 // Decompose into Bezier segments in the s-direction. 216 GeomList<BasicNurbsCurve> segments = CurveUtils.decomposeToBezier(crv); 217 218 // Turn curve segments into a list of control point lists. 219 FastTable<List<ControlPoint>> cpSegments = FastTable.newInstance(); 220 int nSegs = segments.size(); 221 for (int i = 0; i < nSegs; ++i) { 222 BasicNurbsCurve seg = segments.get(i); 223 cpSegments.add(seg.getControlPoints()); 224 } 225 226 // Store the segments in patch segments lists. 227 patchSegmentLists.add(cpSegments); 228 229 // Cleanup 230 cps.reset(); 231 } 232 233 // Rearrange segments into control point networks for each patch. 234 int numPatchRows = patchSegmentLists.get(0).size(); 235 236 GeomList<BasicNurbsSurface> patchSrfList = patches.get(patchCol); 237 FastTable<List<ControlPoint>> patchArray = FastTable.newInstance(); 238 for (int patchRow = 0; patchRow < numPatchRows; ++patchRow) { 239 240 for (int colIdx = 0; colIdx < numColsPerPatch; ++colIdx) { 241 patchArray.add(patchSegmentLists.get(colIdx).get(patchRow)); 242 } 243 244 // Create the control point network. 245 cpNet = ControlPointNet.valueOf(patchArray); 246 patchArray.reset(); 247 248 // Create a surface for this patch. 249 BasicNurbsSurface patch = BasicNurbsSurface.newInstance(cpNet, bezierKVs, bezierKVt); 250 patchSrfList.add(StackContext.outerCopy(patch)); 251 } 252 } 253 254 } finally { 255 StackContext.exit(); 256 } 257 258 return patches; 259 } 260 261 /** 262 * Returns a list containing the parameters at the start (and end) of each Bezier 263 * patch of the specified NURBS surface in the specified direction. This first element 264 * of this list will always be zero and the last will always be 1. 265 * 266 * @param srf The NURBS surface to extract the Bezier patches for. May not be null. 267 * @param dir The direction to return the start parameters in (0=S, 1=T). 268 * @return A list containing the parameters at the start (and end) of each Bezier 269 * patch of the specified NURBS surface in the specified direction. 270 */ 271 public static FastTable<Double> getBezierStartParameters(NurbsSurface srf, int dir) { 272 273 // Extract the knot vector and curve degree. 274 KnotVector kv; 275 if (dir == 0) 276 kv = srf.getSKnotVector(); 277 else 278 kv = srf.getTKnotVector(); 279 int degree = kv.getDegree(); 280 281 // Create a list of interior knots. 282 FastTable<Double> knots = FastTable.newInstance(); 283 int size = kv.length() - degree; 284 double oldS = -1; 285 for (int i = degree; i < size; ++i) { 286 double s = kv.getValue(i); 287 if (s != oldS) { 288 knots.add(s); 289 oldS = s; 290 } 291 } 292 293 return knots; 294 } 295 296}