001/*
002 *   ControlPoint  -- Holds the floating point coordinates of a NURBS control point in nD space.
003 *
004 *   Copyright (C) 2009-2025, Joseph A. Huwaldt
005 *   All rights reserved.
006 *   
007 *   This library is free software; you can redistribute it and/or
008 *   modify it under the terms of the GNU Lesser General Public
009 *   License as published by the Free Software Foundation; either
010 *   version 2.1 of the License, or (at your option) any later version.
011 *   
012 *   This library is distributed in the hope that it will be useful,
013 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
014 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 *   Lesser General Public License for more details.
016 *
017 *   You should have received a copy of the GNU Lesser General Public License
018 *   along with this program; if not, write to the Free Software
019 *   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
020 *   Or visit:  http://www.gnu.org/licenses/lgpl.html
021 */
022package geomss.geom.nurbs;
023
024import geomss.geom.DimensionException;
025import geomss.geom.Point;
026import static java.util.Objects.requireNonNull;
027import java.util.ResourceBundle;
028import javax.measure.converter.ConversionException;
029import javax.measure.quantity.Length;
030import javax.measure.unit.SI;
031import javax.measure.unit.Unit;
032import javolution.context.ObjectFactory;
033import javolution.context.StackContext;
034import javolution.lang.MathLib;
035import javolution.lang.ValueType;
036import javolution.text.Text;
037import javolution.text.TextBuilder;
038import javolution.util.FastTable;
039import javolution.xml.XMLFormat;
040import javolution.xml.XMLSerializable;
041import javolution.xml.stream.XMLStreamException;
042import org.jscience.mathematics.number.Float64;
043import org.jscience.mathematics.vector.Float64Vector;
044
045/**
046 * A container that holds the coordinates of a NURBS control point in n-dimensional space.
047 *
048 * <p> Modified by: Joseph A. Huwaldt </p>
049 *
050 * @author Joseph A. Huwaldt, Date: May 14, 2009
051 * @version February 17, 2025
052 */
053@SuppressWarnings("serial")
054public class ControlPoint implements Cloneable, XMLSerializable, ValueType {
055
056    /**
057     * The resource bundle for this package.
058     */
059    private static final ResourceBundle RESOURCES = geomss.geom.AbstractGeomElement.RESOURCES;
060
061    /**
062     * The coordinates of the physical point represented by this control point.
063     */
064    private Point _point;
065
066    /**
067     * The weighting factor.
068     */
069    private double _weight;
070
071    /**
072     * Returns a {@link ControlPoint} instance of the specified dimension with zero meters
073     * for each coordinate value and zero weight.
074     *
075     * @param dim the physical dimension of the point to create (not including the
076     *            weighting factor).
077     * @return the point having the specified dimension and zero meters for values.
078     */
079    public static ControlPoint newInstance(int dim) {
080        ControlPoint P = ControlPoint.newInstance(Point.newInstance(dim), 0);
081        return P;
082    }
083
084    /**
085     * Returns a {@link ControlPoint} instance of the specified dimension and units with
086     * zero for each coordinate value and zero weight.
087     *
088     * @param dim  the physical dimension of the point to create (not including the
089     *             weighting factor).
090     * @param unit the unit for the point coordinate values. May not be null.
091     * @return the point having the specified dimension &amp; unit and zero for values.
092     */
093    public static ControlPoint newInstance(int dim, Unit<Length> unit) {
094        ControlPoint P = ControlPoint.newInstance(Point.newInstance(dim, requireNonNull(unit)), 0);
095        return P;
096    }
097
098    /**
099     * Returns a 2D {@link ControlPoint} instance holding the specified
100     * <code>double</code> values stated in meters.
101     *
102     * @param x the x value stated in meters.
103     * @param y the y value stated in meters.
104     * @param w the weighting factor for the control point.
105     * @return the point having the specified values.
106     */
107    public static ControlPoint valueOf(double x, double y, double w) {
108        return valueOf(x, y, w, SI.METER);
109    }
110
111    /**
112     * Returns a 2D {@link ControlPoint} instance holding the specified
113     * <code>double</code> values stated in the specified units.
114     *
115     * @param x    the x value stated in the specified unit.
116     * @param y    the y value stated in the specified unit.
117     * @param w    the weighting factor for the control point.
118     * @param unit the unit in which the coordinates are stated. May not be null.
119     * @return the point having the specified values.
120     */
121    public static ControlPoint valueOf(double x, double y, double w, Unit<Length> unit) {
122        ControlPoint P = ControlPoint.newInstance(Point.valueOf(x, y, requireNonNull(unit)), w);
123        return P;
124    }
125
126    /**
127     * Returns a 3D {@link ControlPoint} instance holding the specified
128     * <code>double</code> values stated in meters.
129     *
130     * @param x the x value stated in meters.
131     * @param y the y value stated in meters.
132     * @param z the z value stated in meters.
133     * @param w the weighting factor for the control point.
134     * @return the point having the specified values.
135     */
136    public static ControlPoint valueOf(double x, double y, double z, double w) {
137        return valueOf(x, y, z, w, SI.METER);
138    }
139
140    /**
141     * Returns a 3D {@link ControlPoint} instance holding the specified
142     * <code>double</code> values stated in the specified units.
143     *
144     * @param x    the x value stated in the specified unit.
145     * @param y    the y value stated in the specified unit.
146     * @param z    the z value stated in meters.
147     * @param w    the weighting factor for the control point.
148     * @param unit the unit in which the coordinates are stated. May not be null.
149     * @return the point having the specified values.
150     */
151    public static ControlPoint valueOf(double x, double y, double z, double w, Unit<Length> unit) {
152        ControlPoint P = ControlPoint.newInstance(Point.valueOf(x, y, z, requireNonNull(unit)), w);
153        return P;
154    }
155
156    /**
157     * Returns a {@link ControlPoint} instance holding the specified <code>Float64</code>
158     * values stated in the specified units.
159     *
160     * @param vector the vector of Float64 values stated with all but the last stated in
161     *               the specified unit (the last is assumed to be the weight). May not be
162     *               null.
163     * @param unit   the unit in which the coordinates are stated. May not be null.
164     * @return the point having the specified values.
165     */
166    public static ControlPoint valueOf(org.jscience.mathematics.vector.Vector<Float64> vector, Unit<Length> unit) {
167        requireNonNull(unit);
168        FastTable<Float64> v = FastTable.newInstance();
169        int dims = vector.getDimension() - 1;
170        for (int i = 0; i < dims; ++i) {
171            v.add(vector.get(i));
172        }
173        Float64Vector f64v = Float64Vector.valueOf(v);
174        ControlPoint P = ControlPoint.newInstance(Point.valueOf(f64v, unit), vector.get(dims).doubleValue());
175        return P;
176    }
177
178    /**
179     * Returns a {@link ControlPoint} instance holding the specified <code>Float64</code>
180     * values plus weight stated in the specified units.
181     *
182     * @param vector the vector of Float64 values representing the geometric point stated
183     *               in the specified unit. May not be null.
184     * @param w      the weighting factor for the control point.
185     * @param unit   the unit in which the coordinates are stated. May not be null.
186     * @return the control point having the specified values.
187     */
188    public static ControlPoint valueOf(org.jscience.mathematics.vector.Vector<Float64> vector, double w, Unit<Length> unit) {
189        requireNonNull(vector);
190        requireNonNull(unit);
191        Float64Vector f64v = Float64Vector.valueOf(vector);
192        ControlPoint P = ControlPoint.newInstance(Point.valueOf(f64v, unit), w);
193        return P;
194    }
195
196    /**
197     * Returns a {@link ControlPoint} instance holding the specified <code>double</code>
198     * values stated in the specified units.
199     *
200     * @param unit   the unit in which the coordinates are stated. May not be null.
201     * @param vector the vector of coordinate values with all but the last stated in the
202     *               specified unit (the last is assumed to be the weight). May not be
203     *               null.
204     * @return the control point having the specified values.
205     */
206    public static ControlPoint valueOf(Unit<Length> unit, double[] vector) {
207        requireNonNull(unit);
208        FastTable<Float64> v = FastTable.newInstance();
209        int dims = vector.length - 1;
210        for (int i = 0; i < dims; ++i) {
211            v.add(Float64.valueOf(vector[i]));
212        }
213        Float64Vector f64v = Float64Vector.valueOf(v);
214        ControlPoint P = ControlPoint.newInstance(Point.valueOf(f64v, unit), vector[dims]);
215        return P;
216    }
217
218    /**
219     * Returns a {@link ControlPoint} instance containing the specified Point's data.
220     *
221     * @param point the Point to be copied into a new ControlPoint. May not be null.
222     * @param w     the weighting factor for the control point.
223     * @return the point having the specified values.
224     */
225    public static ControlPoint valueOf(Point point, double w) {
226        ControlPoint P = ControlPoint.newInstance(requireNonNull(point), w);
227        return P;
228    }
229
230    /**
231     * Returns a {@link ControlPoint} instance containing the specified ControlPoint's
232     * data.
233     *
234     * @param point the ControlPoint to be copied into a new ControlPoint. May not be
235     *              null.
236     * @return the point having the specified values.
237     */
238    public static ControlPoint valueOf(ControlPoint point) {
239        return copyOf(requireNonNull(point));
240    }
241
242    /**
243     * Returns the number of physical dimensions of the geometry element. This
244     * implementation returns the dimensions of the point and does not count the weighting
245     * factor.
246     *
247     * @return The physical dimensions of the control points not counting the weighting
248     *         factor.
249     */
250    public int getPhyDimension() {
251        return _point.getPhyDimension();
252    }
253
254    /**
255     * Returns the value of the Parameter in this point as a <code>double</code>, stated
256     * in this point's unit.
257     *
258     * @param i the dimension index.
259     * @return the value of the Parameter at <code>i</code>.
260     * @throws IndexOutOfBoundsException <code>(i &lt; 0) || (i &gt;
261     * getPhyDimension() - 1)</code>
262     */
263    public double getValue(int i) {
264        return _point.getValue(i);
265    }
266
267    /**
268     * Return the Point representation of this ControlPoint.
269     *
270     * @return A Point representation of this control point.
271     */
272    public Point getPoint() {
273        return _point;
274    }
275
276    /**
277     * Return the weight associated with this control point.
278     *
279     * @return The weighting factor associated with this control point.
280     */
281    public double getWeight() {
282        return _weight;
283    }
284
285    /**
286     * Return a new control point that is identical to this one, but with the weight
287     * changed to the specified value.
288     *
289     * @param value The new weighting factor.
290     * @return A new ControlPoint identical to this one but with the specified weight.
291     */
292    public ControlPoint changeWeight(double value) {
293        ControlPoint cp = ControlPoint.newInstance(_point, value);
294        return cp;
295    }
296
297    /**
298     * Return a copy of this control point with the values made homogeneous (all the
299     * geometric point values are divided by the weight).
300     *
301     * @return A new ControlPoint with the values made homogeneous.
302     */
303    public ControlPoint getHomogeneous() {
304        Point newPoint = _point.divide(_weight);
305        return ControlPoint.newInstance(newPoint, _weight);
306    }
307
308    /**
309     * Returns a new ControlPoint with the geometric point portion of this control point
310     * multiplied by the weight.
311     *
312     * @return A new ControlPoint with the geometric point multiplied by the weight.
313     */
314    public ControlPoint applyWeight() {
315        Point newPoint = _point.times(_weight);
316        return ControlPoint.newInstance(newPoint, _weight);
317    }
318
319    /**
320     * Returns a new control point with the sum of this point with the one specified.
321     *
322     * @param that the point to be added to this one. May not be null.
323     * @return A new ControlPoint with this + that.
324     * @throws DimensionException if point dimensions are different.
325     */
326    public ControlPoint plus(ControlPoint that) {
327        Point pnt = _point.plus(that._point);
328        double weight = this._weight + that._weight;
329        return ControlPoint.newInstance(pnt, weight);
330    }
331
332    /**
333     * Returns a new control point with the difference between this point with the one
334     * specified.
335     *
336     * @param that the point to be subtracted from this one. May not be null.
337     * @return A new ControlPoint with this - that.
338     * @throws DimensionException if point dimensions are different.
339     */
340    public ControlPoint minus(ControlPoint that) {
341        Point pnt = _point.minus(that._point);
342        double weight = this._weight - that._weight;
343        return ControlPoint.newInstance(pnt, weight);
344    }
345
346    /**
347     * Returns a new control point with the product of this point with the specified
348     * coefficient.
349     *
350     * @param k the coefficient multiplier.
351     * @return A new ControlPoint with this*k.
352     */
353    public ControlPoint times(double k) {
354        Point pnt = _point.times(k);
355        double weight = _weight * k;
356        return ControlPoint.newInstance(pnt, weight);
357    }
358
359    /**
360     * Returns a new control point with this point divided by the specified divisor.
361     *
362     * @param divisor the divisor.
363     * @return A new ControlPoint with this / divisor.
364     */
365    public ControlPoint divide(double divisor) {
366        return times(1.0 / divisor);
367    }
368
369    /**
370     * Returns the norm, magnitude, or length value of the vector from the origin to this
371     * control point geometric point (square root of the dot product of the
372     * origin-to-this-point vector and itself).
373     *
374     * @return The magnitude of the vector from the origin to this control point.
375     *         geometric point.
376     */
377    public double normValue() {
378        return _point.normValue();
379    }
380
381    /**
382     * Return the distance (in homogeneous coordinates) between this control point and
383     * another (this treats the weight simply as another dimension).
384     *
385     * @param cp The control point to find the homogeneous distance to. May not be null.
386     * @return The homogeneous distance to the specified control point from this one.
387     */
388    public double distance(ControlPoint cp) {
389        double d2 = 0;
390        int dim = _point.getPhyDimension();
391        Point cpPoint = cp._point;
392        if (cpPoint.getPhyDimension() != dim)
393            throw new DimensionException(RESOURCES.getString("dimensionErr"));
394
395        for (int i = 0; i < dim; ++i) {
396            double v = _point.getValue(i) - cpPoint.getValue(i);
397            d2 += v * v;
398        }
399        double v = _weight - cp._weight;
400        d2 += v * v;
401        return MathLib.sqrt(d2);
402    }
403
404    /**
405     * Return the coordinate point representing the minimum bounding box corner (e.g.: min
406     * X, min Y, min Z).
407     *
408     * @return The minimum bounding box coordinate for this geometry element.
409     */
410    public Point getBoundsMin() {
411        return _point;
412    }
413
414    /**
415     * Return the coordinate point representing the maximum bounding box corner (e.g.: max
416     * X, max Y, max Z).
417     *
418     * @return The maximum bounding box coordinate for this geometry element.
419     */
420    public Point getBoundsMax() {
421        return _point;
422    }
423
424    /**
425     * Return <code>true</code> if this control point contains valid and finite numerical
426     * components. A value of <code>false</code> will be returned if any of the coordinate
427     * values are NaN or Inf (including the weight).
428     *
429     * @return true if this control point contains valid and finite values.
430     */
431    public boolean isValid() {
432        return _point.isValid() && !(Double.isNaN(_weight) || Double.isInfinite(_weight));
433    }
434
435    /**
436     * Returns the unit in which this control point is stated.
437     *
438     * @return The unit in which this control point is stated.
439     */
440    public Unit<Length> getUnit() {
441        return _point.getUnit();
442    }
443
444    /**
445     * Returns the equivalent to this control point but stated in the specified unit.
446     *
447     * @param unit The length unit of the control point to be returned. May not be null.
448     * @return An equivalent to this control point but stated in the specified unit.
449     * @throws ConversionException if the the input unit is not a length unit.
450     */
451    public ControlPoint to(Unit<Length> unit) throws ConversionException {
452        if (unit.equals(getUnit()))
453            return this;
454        return ControlPoint.newInstance(_point.to(unit), _weight);
455    }
456
457    /**
458     * Returns the values stored in this control point, with the coordinate points stated
459     * in this point's unit and the weight tacked onto the end as a Float64Vector.
460     *
461     * @return A Float64Vector representation of this control point.
462     */
463    public Float64Vector toFloat64Vector() {
464        StackContext.enter();
465        try {
466
467            FastTable<Float64> v = FastTable.newInstance();
468            Float64Vector p3dv = _point.toFloat64Vector();
469            int numDims = p3dv.getDimension();
470            for (int i = 0; i < numDims; ++i) {
471                v.add(p3dv.get(i));
472            }
473            v.add(Float64.valueOf(_weight));
474            Float64Vector f64v = Float64Vector.valueOf(v);
475            return StackContext.outerCopy(f64v);
476
477        } finally {
478            StackContext.exit();
479        }
480    }
481
482    /**
483     * Returns the values stored in this control point as a Java array, stated in the
484     * current {@link #getUnit units} with the weight tacked onto the end of the array.
485     *
486     * @return A new array with the control point values copied into it with the
487     *          weight at the end.
488     */
489    public double[] toArray() {
490        return toArray(new double[this.getPhyDimension()+1]);
491    }
492
493    /**
494     * Returns the values stored in this control point, with the coordinate point stated in the current
495     * {@link #getUnit units} and the weight tacked onto the end, stored in the input Java array.
496     *
497     * @param array An existing array that has at least as many elements as the physical
498     *              dimension of this point + 1 for the weight.
499     * @return A reference to the input array after the control point values have been copied over
500     *         to it.
501     * @see #getPhyDimension()
502     */
503    public double[] toArray(double[] array) {
504        _point.toArray(array);
505        array[_point.getPhyDimension()] = _weight;
506        return array;
507    }
508
509    /**
510     * Returns a copy of this ControlPoint instance
511     * {@link javolution.context.AllocatorContext allocated} by the calling thread
512     * (possibly on the stack).
513     *
514     * @return an identical and independent copy of this point.
515     */
516    @Override
517    public ControlPoint copy() {
518        return copyOf(this);
519    }
520
521    /**
522     * Returns a copy of this ControlPoint instance
523     * {@link javolution.context.AllocatorContext allocated} by the calling thread
524     * (possibly on the stack).
525     *
526     * @return an identical and independent copy of this point.
527     * @throws java.lang.CloneNotSupportedException Never thrown.
528     */
529    @Override
530    @SuppressWarnings("CloneDoesntCallSuperClone")
531    public Object clone() throws CloneNotSupportedException {
532        return copy();
533    }
534
535    /**
536     * Compares this ControlPoint against the specified object for strict equality (same
537     * values and same units).
538     *
539     * @param obj the object to compare with.
540     * @return <code>true</code> if this point is identical to that point;
541     *         <code>false</code> otherwise.
542     */
543    @Override
544    public boolean equals(Object obj) {
545        if (this == obj)
546            return true;
547        if ((obj == null) || (obj.getClass() != this.getClass()))
548            return false;
549
550        ControlPoint that = (ControlPoint)obj;
551        return this._point.equals(that._point) && this._weight == that._weight;
552    }
553
554    /**
555     * Returns the hash code for this parameter.
556     *
557     * @return the hash code value.
558     */
559    @Override
560    public int hashCode() {
561        int hash = 7;
562        hash = hash * 31 + makeVarCode(_weight);
563        hash = hash * 31 + _point.hashCode();
564        return hash;
565    }
566
567    private static int makeVarCode(double value) {
568        long bits = Double.doubleToLongBits(value);
569        int var_code = (int)(bits ^ (bits >>> 32));
570        return var_code;
571    }
572
573    /**
574     * Returns the text representation of this control point that consists of the the
575     * geometry coordinate values followed by the weighting factor. For example:
576     * <pre>
577     *   {{10 ft, -3 ft, 4.56 ft}, 1.0}
578     * </pre>
579     *
580     * @return the text representation of this geometry element.
581     */
582    public Text toText() {
583        TextBuilder tmp = TextBuilder.newInstance();
584        tmp.append('{');
585        tmp.append(_point.toText());
586        tmp.append(", ");
587        tmp.append(_weight);
588        tmp.append('}');
589        Text txt = tmp.toText();
590        TextBuilder.recycle(tmp);
591        return txt;
592    }
593
594    /**
595     * Returns the String representation of this control point that consists of the the
596     * geometry coordinate values followed by the weighting factor. For example:
597     * <pre>
598     *   {{10 ft, -3 ft, 4.56 ft}, 1.0}
599     * </pre>
600     *
601     * @return the text representation of this geometry element.
602     */
603    @Override
604    public String toString() {
605        return toText().toString();
606    }
607
608    /**
609     * Holds the default XML representation for this object.
610     */
611    protected static final XMLFormat<ControlPoint> XML = new XMLFormat<ControlPoint>(ControlPoint.class) {
612        @Override
613        public ControlPoint newInstance(Class<ControlPoint> cls, InputElement xml) throws XMLStreamException {
614            return FACTORY.object();
615        }
616
617        @Override
618        public void read(InputElement xml, ControlPoint obj) throws XMLStreamException {
619
620            double weight = xml.getAttribute("weight", 1.0);
621            obj._weight = weight;
622
623            Point point = xml.getNext();
624            obj._point = point;
625
626        }
627
628        @Override
629        public void write(ControlPoint obj, OutputElement xml) throws XMLStreamException {
630
631            xml.setAttribute("weight", obj._weight);
632            xml.add(obj._point);
633
634        }
635    };
636
637    ///////////////////////
638    // Factory creation. //
639    ///////////////////////
640    private ControlPoint() {
641    }
642
643    @SuppressWarnings("unchecked")
644    private static final ObjectFactory<ControlPoint> FACTORY = new ObjectFactory<ControlPoint>() {
645        @Override
646        protected ControlPoint create() {
647            return new ControlPoint();
648        }
649
650        @Override
651        protected void cleanup(ControlPoint obj) {
652            obj._point = null;
653        }
654    };
655
656    @SuppressWarnings("unchecked")
657    private static ControlPoint newInstance(Point point, double weight) {
658        ControlPoint o = FACTORY.object();
659        o._point = point;
660        o._weight = weight;
661        return o;
662    }
663
664    @SuppressWarnings("unchecked")
665    private static ControlPoint copyOf(ControlPoint original) {
666        ControlPoint o = FACTORY.object();
667        o._point = original._point.copy();
668        o._weight = original._weight;
669        return o;
670    }
671
672}