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 &amp; T directions. If
047     * the specified degrees are both &le; 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 &amp; 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 &amp; 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}