001/*
002 *   Entity126_BSplineCurve  -- An entity representing a Rational B-Spline Curve.
003 *
004 *   Copyright (C) 2010-2025, Joseph A. Huwaldt. All rights reserved.
005 *   
006 *   part library is free software; you can redistribute it and/or
007 *   modify it under the terms of the GNU Lesser General Public
008 *   License as published by the Free Software Foundation; either
009 *   version 2.1 of the License, or (at your option) any later version.
010 *   
011 *   part library is distributed in the hope that it will be useful,
012 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
013 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 *   Lesser General Public License for more details.
015 *
016 *   You should have received a copy of the GNU Lesser General Public License
017 *   along with part program; if not, write to the Free Software
018 *   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
019 *   Or visit:  http://www.gnu.org/licenses/lgpl.html
020 */
021package geomss.geom.reader.iges;
022
023import geomss.geom.GeomElement;
024import geomss.geom.Point;
025import geomss.geom.Transformable;
026import geomss.geom.nurbs.BasicNurbsCurve;
027import geomss.geom.nurbs.ControlPoint;
028import geomss.geom.nurbs.KnotVector;
029import geomss.geom.nurbs.NurbsCurve;
030import jahuwaldt.js.param.Parameter;
031import jahuwaldt.tools.math.MathTools;
032import java.io.IOException;
033import java.io.PrintWriter;
034import java.io.RandomAccessFile;
035import java.text.MessageFormat;
036import java.util.ArrayList;
037import java.util.List;
038import javax.measure.quantity.Length;
039
040/**
041 * <p>
042 * <b><i>RATIONAL B-SPLINE CURVE ENTITY</i></b> - This entity represents a Rational
043 * B-Spline Curve that may be isolated or used as a component of a Composite Curve Entity
044 * or a Subfigure Entity. This represents a parametric equation obtained by dividing two
045 * summations involving weights (which are real numbers), the control points, and B-Spline
046 * basis functions.</p>
047 *
048 * <p>
049 * This entity, when read from an IGES file, is converted to a NURBS curve. This entity
050 * type <b>can</b> be written out to an IGES file. When reading in this entity, all plane
051 * parameters are stored in the user data with the prefix "IGES_126_" followed by the
052 * parameter name.
053 * </p>
054 *
055 * <p> Modified by: Joseph A. Huwaldt </p>
056 * 
057 * @author Joseph A. Huwaldt, Date: August 23, 2010
058 * @version February 22, 2025
059 */
060public class Entity126_BSplineCurve extends GeomSSEntity {
061
062    private int prop1 = 0;  //  0=non-planar, 1=planar
063    private int prop2 = 0;  //  0=open, 1=closed
064    private int prop3 = 0;  //  0=rational, 1=polynomial
065    private int prop4 = 0;  //  0=non-periodic (clamped), 1=periodic (un-clamped).
066    private double[] knots = null;  //  Knot sequence
067    private double ustart = 0;  //  Starting parameter value.
068    private double uend = 1;    //  Ending parameter value.
069    private double xnorm = 0;   //  Unit normal (if curve is planar; flag1=1)
070    private double ynorm = 0;
071    private double znorm = 1;
072
073    private NurbsCurve curve;   //  The GeomSS curve this entity represents.
074
075    /**
076     * Default constructor.
077     *
078     * @param p  part to which this entity is contained
079     * @param de Directory Entry for this entity
080     */
081    public Entity126_BSplineCurve(Part p, DirEntry de) {
082        super(p, de);
083
084        if (Constants.DEBUG) {
085            System.out.println("Entity126 constructor called");
086        }
087
088    }
089
090    /**
091     * Create this entity from the specified GeomSS geometry element.
092     *
093     * @param part  The Part to which this entity is contained.
094     * @param DEnum The line count from the start of the Directory Entry Section for this
095     *              entry (odd number).
096     * @param geom  The GeomSS GeomPoint geometry to return an Entity for.
097     */
098    public Entity126_BSplineCurve(Part part, int DEnum, NurbsCurve geom) {
099        super(part, new DirEntry(126, 0, DEnum, 0, geom.getName()));
100        curve = geom;
101
102        //  Store off the Knot vector also.
103        KnotVector kv = curve.getKnotVector();
104        int n = kv.length();
105        knots = new double[n];
106        for (int i = 0; i < n; ++i)
107            knots[i] = kv.getValue(i);
108
109        //  Is the curve planar?
110        Parameter<Length> tol = Parameter.valueOf(Constants.Grain, Constants.unit);
111        if (curve.isPlanar(tol))
112            prop1 = 1;
113
114        //  Is the curve closed?
115        if (curve.getRealPoint(0).distance(curve.getRealPoint(1)).isLessThan(tol))
116            prop2 = 1;
117
118        //  Is the curve rational (prop3 = 0) or polynomial (prop3 = 1)?
119        List<ControlPoint> cps = curve.getControlPoints();
120        n = cps.size();
121        double w = cps.get(0).getWeight();
122        prop3 = 1;
123        for (int i = 1; i < n; ++i) {
124            ControlPoint cp = cps.get(i);
125            if (Math.abs(cp.getWeight() - w) > MathTools.EPS) {
126                prop3 = 0;
127                break;
128            }
129        }
130    }
131
132    /**
133     * Checks to see if the entity is correct. The following restrictions are imposed:
134     *
135     * - The Label Display Pointer shall be 0
136     */
137    @Override
138    public void check() {
139        DirEntry DE = getDirectoryEntry();
140
141        // DE LblDsp shall be 0
142        if (DE.getLblDsp() != 0) {
143            String msg = MessageFormat.format(RESOURCES.getString("labelDisplay"), DE.getLblDsp());
144            addErrorMessage(getWarningString(msg));
145        }
146
147    }
148
149    /**
150     * Read the Parameter Data from the String read in by the superclass.
151     *
152     * @param in input file
153     * @throws java.io.IOException if the parameter data could not be read in.
154     */
155    @Override
156    public void read(RandomAccessFile in) throws IOException {
157
158        if (Constants.DEBUG) {
159            System.out.println("Entity126.read() called");
160        }
161
162        super.read(in);
163        String s = getPDString();
164
165        if (Constants.DEBUG) {
166            System.out.println("PD String = \"" + s + "\"");
167        }
168
169        int K = getInt(s);          //  Number of control points.
170        int M = getInt(s);          //  Degree of basis functions.
171        prop1 = getInt(s);
172        prop2 = getInt(s);
173        prop3 = getInt(s);
174        prop4 = getInt(s);
175
176        int n = K + 1 + M + 1;      //  Number of knots.
177        knots = new double[n];
178        for (int i = 0; i < n; ++i) {
179            double u = getReal(s);
180            knots[i] = u;
181        }
182
183        List<Double> weights = new ArrayList(); //  List of weights for each control point.
184        for (int i = 0; i <= K; ++i) {
185            double w = getReal(s);
186            weights.add(w);
187        }
188
189        List<Point> cps = new ArrayList();      //  List of control points.
190        for (int i = 0; i <= K; ++i) {
191            cps.add(getPoint3(s));
192        }
193
194        ustart = getReal(s);
195        uend = getReal(s);
196
197        xnorm = getReal(s);
198        if (Math.abs(xnorm) < Constants.Grain)
199            xnorm = 0;
200        ynorm = getReal(s);
201        if (Math.abs(ynorm) < Constants.Grain)
202            ynorm = 0;
203        znorm = getReal(s);
204        if (Math.abs(znorm) < Constants.Grain)
205            znorm = 0;
206
207        //  Make sure all the knots read in are in-range.
208        for (int i = 0; i < n; ++i) {
209            double kv = knots[i];
210            if (kv < ustart)
211                knots[i] = ustart;
212            else if (kv > uend)
213                knots[i] = uend;
214        }
215
216        //  Scale the knots into the range 0-1.
217        double[] scaledKnots = knots;
218        if (Math.abs(ustart) > MathTools.EPS || Math.abs(uend - 1.0) > MathTools.EPS) {
219            scaledKnots = new double[n];
220            double m = 1. / (uend - ustart);
221            double b = -m * ustart;
222            for (int i = 0; i < n; ++i) {
223                double kv = knots[i];
224                kv = scaleKnot(m, b, kv);
225                scaledKnots[i] = kv;
226            }
227        }
228
229        //  Create the knot vector.
230        KnotVector kv = KnotVector.newInstance(M, scaledKnots);
231
232        //  Create the control points.
233        List<ControlPoint> cpList = new ArrayList();
234        n = cps.size();
235        for (int i = 0; i < n; ++i) {
236            double weight = weights.get(i);
237            Point pnt = cps.get(i);
238            cpList.add(ControlPoint.valueOf(pnt, weight));
239        }
240
241        //  Create the curve.
242        curve = BasicNurbsCurve.newInstance(cpList, kv);
243
244        super.read_additional();
245    }
246
247    /**
248     * The GeomSS geometry element is created from the IGES parameters when this method is
249     * called.
250     */
251    @Override
252    void createGeometry() throws IOException {
253        //  Everything done in "read()".
254    }
255
256    /**
257     * Scale the knot value in the the required range from 0.0 to 1.0.
258     */
259    private double scaleKnot(double m, double b, double value) {
260        //  Scale the knot value.
261        double kv = m * value + b;
262
263        //  Watch for roundoff.
264        if (kv < 0.0)
265            kv = 0.0;
266        else if (kv > 1.0)
267            kv = 1.0;
268
269        return kv;
270    }
271
272    /**
273     * Method used to apply IGES meta-data to GeomSS elements. This implementation stores
274     * in the user data, in addition to the header info, all the parameter information for
275     * this entity type prefixed by "IGES_126_".
276     */
277    @Override
278    protected void applyMetaData(GeomElement element) {
279        super.applyMetaData(element);
280        element.putUserData("IGES_126_PROP1", prop1);
281        element.putUserData("IGES_126_PROP2", prop2);
282        element.putUserData("IGES_126_PROP3", prop3);
283        element.putUserData("IGES_126_PROP4", prop4);
284        element.putUserData("IGES_U0", ustart);
285        element.putUserData("IGES_U1", uend);
286        element.putUserData("IGES_126_XNORM", xnorm);
287        element.putUserData("IGES_126_YNORM", ynorm);
288        element.putUserData("IGES_126_ZNORM", znorm);
289    }
290
291    /**
292     * Return a reference to the Transformable GeomElement contained in this IGES Entity.
293     *
294     * @return A reference to the Transformable GeomElement contained in this IGES Entity.
295     */
296    @Override
297    protected Transformable getGeomElement() {
298        return curve;
299    }
300
301    /**
302     * Returns <code>true</code> if the Entity can be written to an exchange file.
303     *
304     * @return true
305     */
306    @Override
307    public boolean canWrite() {
308        return true;
309    }
310
311    /**
312     * Write this entity's parameter data to the specified PrintWriter.
313     *
314     * @param writer The PrintWriter to write the parameter data for this entity to.
315     * @param PDnum  The starting Parameter Data row index number.
316     * @return The Parameter Data row index number for the next row.
317     * @throws java.io.IOException if the parameter data could not be written out.
318     */
319    @Override
320    public int write(PrintWriter writer, int PDnum) throws IOException {
321
322        //  Build up the parameter data string.
323        NurbsCurve curve2 = curve.to(Constants.unit);
324        List<ControlPoint> cps = curve2.getControlPoints();
325        int K = cps.size() - 1;
326        KnotVector kv = curve2.getKnotVector();
327        int M = kv.getDegree();
328
329        StringBuilder buffer = new StringBuilder();
330        buffer.append(126);                 buffer.append(Constants.Delim);
331        buffer.append(K);                   buffer.append(Constants.Delim);
332        buffer.append(M);                   buffer.append(Constants.Delim);
333        buffer.append(prop1);               buffer.append(Constants.Delim);
334        buffer.append(prop2);               buffer.append(Constants.Delim);
335        buffer.append(prop3);               buffer.append(Constants.Delim);
336        buffer.append(prop4);               buffer.append(Constants.Delim);
337
338        int n = kv.length();
339        for (int i = 0; i < n; ++i) {
340            double u = kv.getValue(i);
341            buffer.append(u);   buffer.append(Constants.Delim);
342        }
343
344        for (int i = 0; i <= K; ++i) {
345            double w = cps.get(i).getWeight();
346            buffer.append(w);   buffer.append(Constants.Delim);
347        }
348
349        for (int i = 0; i <= K; ++i) {
350            Point cp = cps.get(i).getPoint();
351            appendPoint3(buffer, cp);
352        }
353
354        buffer.append(ustart);  buffer.append(Constants.Delim);
355        buffer.append(uend);    buffer.append(Constants.Delim);
356
357        if (Math.abs(xnorm) < Constants.Grain)
358            xnorm = 0;
359        buffer.append(xnorm);   buffer.append(Constants.Delim);
360        if (Math.abs(ynorm) < Constants.Grain)
361            ynorm = 0;
362        buffer.append(ynorm);   buffer.append(Constants.Delim);
363        if (Math.abs(znorm) < Constants.Grain)
364            znorm = 0;
365        buffer.append(znorm);   buffer.append(Constants.Term);
366
367        //  Write it out.
368        int oldPDnum = PDnum;
369        PDnum = Constants.writeSection(writer, PDnum, Constants.makeSequenceNumber(getDENum()),
370                'P', buffer);
371
372        //  Store the PD line number and line count in the directory entry.
373        getDirectoryEntry().setPDNumber(oldPDnum, PDnum - oldPDnum);
374
375        return PDnum;
376    }
377
378    /**
379     * Returns a short String describing this Entity object's type.
380     *
381     * @return A short String describing this Entity object's type.
382     */
383    @Override
384    public String getTypeString() {
385        return "Entity126 - Rational B-Spline Curve";
386    }
387
388    /**
389     * Dump to String.
390     *
391     * @return String containing the resulting text.
392     */
393    @Override
394    public String toString() {
395        if (curve == null)
396            return super.toString();
397        
398        StringBuilder outStr = new StringBuilder(super.toString());
399        outStr.append("\n");
400        
401        //  Get the data we need.
402        List<ControlPoint> cps = curve.getControlPoints();
403        int K = cps.size()-1;
404        KnotVector kv = curve.getKnotVector();
405        int M = kv.getDegree();
406        
407        outStr.append("K     = ");  outStr.append(K);       outStr.append("\n");
408        outStr.append("M     = ");  outStr.append(M    );   outStr.append("\n");
409        outStr.append("prop1 = ");  outStr.append(prop1);   outStr.append("\n");
410        outStr.append("prop2 = ");  outStr.append(prop2);   outStr.append("\n");
411        outStr.append("prop3 = ");  outStr.append(prop3);   outStr.append("\n");
412        outStr.append("prop4 = ");  outStr.append(prop4);   outStr.append("\n");
413        
414        int n = knots.length;
415        for (int i = 0; i < n; i++) {
416            outStr.append("T(");            outStr.append(i);   outStr.append("):  ");
417            outStr.append(knots[i]);        outStr.append("\n");
418        }
419        outStr.append("\n");
420        
421        
422        for (int i=0; i <= K; i++) {
423            double weight = cps.get(i).getWeight();
424            outStr.append("W(");            outStr.append(i);   outStr.append("):  ");
425            outStr.append(weight);          outStr.append("\n");
426        }
427
428        for (int i = 0; i <= K; i++) {
429            Point cp = cps.get(i).getPoint();
430            outStr.append("X(");        outStr.append(i);   outStr.append("):  ");
431            outStr.append(cp.get(0));   outStr.append("\n");
432            outStr.append("Y(");        outStr.append(i);   outStr.append("):  ");
433            outStr.append(cp.get(1));   outStr.append("\n");
434            outStr.append("Z(");        outStr.append(i);   outStr.append("):  ");
435            outStr.append(cp.get(2));   outStr.append("\n");
436        }
437        
438        outStr.append("ustart = "); outStr.append(ustart);  outStr.append("\n");
439        outStr.append("uend   = "); outStr.append(uend);    outStr.append("\n");
440        
441        if (prop1 == 1) {
442            outStr.append("xnorm = ");  outStr.append(xnorm);   outStr.append("\n");
443            outStr.append("ynorm = ");  outStr.append(ynorm);   outStr.append("\n");
444            outStr.append("znorm = ");  outStr.append(znorm);   outStr.append("\n");
445        }
446        
447        return outStr.toString();
448    }
449
450}