001/**
002 * MutablePoint -- Holds the changeable floating point coordinates of a point in nD space.
003 *
004 * Copyright (C) 2013-2025, 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.1 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 Lesser 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 */
018package geomss.geom;
019
020import jahuwaldt.js.param.Coordinate3D;
021import jahuwaldt.js.param.Parameter;
022import java.text.MessageFormat;
023import java.util.List;
024import java.util.Objects;
025import static java.util.Objects.requireNonNull;
026import javax.measure.converter.ConversionException;
027import javax.measure.quantity.Length;
028import javax.measure.quantity.Quantity;
029import javax.measure.unit.SI;
030import javax.measure.unit.Unit;
031import javolution.context.ObjectFactory;
032import javolution.util.FastTable;
033import javolution.xml.XMLFormat;
034import javolution.xml.stream.XMLStreamException;
035import org.jscience.mathematics.number.Float64;
036import org.jscience.mathematics.vector.Float64Vector;
037
038/**
039 * A mutable container that holds changeable coordinates of a point in n-dimensional
040 * space.
041 *
042 * <p> Modified by: Joseph A. Huwaldt </p>
043 *
044 * @author Joseph A. Huwaldt, Date: December 11, 1999
045 * @version February 17, 2025
046 */
047@SuppressWarnings({"serial", "CloneableImplementsClone"})
048public final class MutablePoint extends GeomPoint {
049
050    //  Use an immutable Point as a backing for the MutablePoint.
051    private Point _point;
052
053    /**
054     * Returns a {@link MutablePoint} instance of the specified dimension with zero meters
055     * for each coordinate value.
056     *
057     * @param dim the physical dimension of the point to create.
058     * @return the point having the specified dimension and zero meters for values.
059     */
060    public static MutablePoint newInstance(int dim) {
061        MutablePoint V = FACTORY.object();
062        V._point = Point.newInstance(dim);
063        return V;
064    }
065
066    /**
067     * Returns a {@link Point} instance of the specified dimension and units with zero for
068     * each coordinate value.
069     *
070     * @param dim  the physical dimension of the point to create.
071     * @param unit The unit for the point to create. May not be null.
072     * @return the point having the specified dimension &amp; units and zero for values.
073     */
074    public static MutablePoint newInstance(int dim, Unit<Length> unit) {
075        MutablePoint V = FACTORY.object();
076        V._point = Point.newInstance(dim, requireNonNull(unit));
077        return V;
078    }
079
080    /**
081     * Returns a {@link MutablePoint} instance holding the specified <code>double</code>
082     * value or values stated in meters.
083     *
084     * @param x the coordinate values stated in meters. May not be null.
085     * @return the point having the specified value.
086     */
087    public static MutablePoint valueOf(double... x) {
088        return valueOf(SI.METER, x);
089    }
090
091    /**
092     * Returns a 1D {@link MutablePoint} instance holding the specified
093     * <code>double</code> values stated in the specified units.
094     *
095     * @param x    the x value stated in the specified unit.
096     * @param unit the unit in which the coordinates are stated. May not be null.
097     * @return the point having the specified value.
098     */
099    public static MutablePoint valueOf(double x, Unit<Length> unit) {
100        MutablePoint V = FACTORY.object();
101        V._point = Point.valueOf(x, requireNonNull(unit));
102        return V;
103    }
104
105    /**
106     * Returns a 2D {@link MutablePoint} instance holding the specified
107     * <code>double</code> value stated in the specified units.
108     *
109     * @param x    the x value stated in the specified unit.
110     * @param y    the y value stated in the specified unit.
111     * @param unit the unit in which the coordinates are stated. May not be null.
112     * @return the point having the specified values.
113     */
114    public static MutablePoint valueOf(double x, double y, Unit<Length> unit) {
115        MutablePoint V = FACTORY.object();
116        V._point = Point.valueOf(x, y, requireNonNull(unit));
117        return V;
118    }
119
120    /**
121     * Returns a 3D {@link MutablePoint} instance holding the specified
122     * <code>double</code> values stated in the specified units.
123     *
124     * @param x    the x value stated in the specified unit.
125     * @param y    the y value stated in the specified unit.
126     * @param z    the z value stated in the specified unit.
127     * @param unit the unit in which the coordinates are stated. May not be null.
128     * @return the point having the specified values.
129     */
130    public static MutablePoint valueOf(double x, double y, double z, Unit<Length> unit) {
131        MutablePoint V = FACTORY.object();
132        V._point = Point.valueOf(x, y, z, requireNonNull(unit));
133        return V;
134    }
135
136    /**
137     * Returns a {@link MutablePoint} instance holding the specified <code>double</code>
138     * values stated in the specified units.
139     *
140     * @param unit   the length unit in which the coordinates are stated. May not be null.
141     * @param values the list of values stated in the specified unit. May not be null.
142     * @return the point having the specified values.
143     */
144    public static MutablePoint valueOf(Unit<Length> unit, double... values) {
145        MutablePoint V = FACTORY.object();
146        V._point = Point.valueOf(requireNonNull(unit), requireNonNull(values));
147        return V;
148    }
149
150    /**
151     * Returns a {@link MutablePoint} instance holding the specified
152     * <code>Parameter</code> values. All the values are converted to the same units as
153     * the 1st value.
154     *
155     * @param values The list of values to be stored. May not be null.
156     * @return the point having the specified values in the units of the 1st value.
157     */
158    public static MutablePoint valueOf(Parameter<Length>... values) {
159        MutablePoint V = FACTORY.object();
160        V._point = Point.valueOf(requireNonNull(values));
161        return V;
162    }
163
164    /**
165     * Returns a {@link MutablePoint} instance holding the specified
166     * <code>Parameter</code> values. All the values are converted to the same units as
167     * the 1st value.
168     *
169     * @param values The list of values to be stored. May not be null.
170     * @return the point having the specified values in the units of the 1st value.
171     */
172    public static MutablePoint valueOf(List<Parameter<Length>> values) {
173        MutablePoint V = FACTORY.object();
174        V._point = Point.valueOf(requireNonNull(values));
175        return V;
176    }
177
178    /**
179     * Returns a {@link MutablePoint} instance holding the specified
180     * <code>@link jahuwaldt.js.param.Coordinate3D Coordinate3D</code> values.
181     *
182     * @param coord The {@link jahuwaldt.js.param.Coordinate3D Coordinate3D} to be stored.
183     *              May not be null.
184     * @return the point having the specified values.
185     */
186    public static MutablePoint valueOf(Coordinate3D<Length> coord) {
187        MutablePoint P = FACTORY.object();
188        P._point = Point.valueOf(requireNonNull(coord));
189        return P;
190    }
191
192    /**
193     * Returns a {@link MutablePoint} instance containing the specified vector of Float64
194     * values stated in the specified units.
195     *
196     * @param vector the vector of Float64 values stated in the specified unit. May not be null.
197     * @param unit   the unit in which the values are stated. May not be null.
198     * @return the point having the specified values.
199     */
200    public static MutablePoint valueOf(org.jscience.mathematics.vector.Vector<Float64> vector, Unit<Length> unit) {
201        MutablePoint V = FACTORY.object();
202        V._point = Point.valueOf(requireNonNull(vector), requireNonNull(unit));
203        return V;
204    }
205
206    /**
207     * Returns a {@link MutablePoint} instance containing the specified vector of
208     * Parameter values with length units. All the values are converted to the same units
209     * as the 1st value.
210     *
211     * @param <Q>    The Quantity (unit type) of this point.
212     * @param vector the vector of Parameter values stated in length or dimensionless
213     *               units. If in dimensionless units, the values are interpreted as being
214     *               in meters. May not be null.
215     * @return the point having the specified values.
216     * @throws ConversionException if the input vector is not in length (or Dimensionless)
217     * units.
218     */
219    public static <Q extends Quantity> MutablePoint valueOf(org.jscience.mathematics.vector.Vector<Parameter<Q>> vector) {
220        MutablePoint P = FACTORY.object();
221        P._point = Point.valueOf(requireNonNull(vector));
222        return P;
223    }
224
225    /**
226     * Returns a {@link MutablePoint} instance that represents the direction elements of
227     * the specified GeomVector given in length or dimensionless units. If the user wishes
228     * for the point to represent the end or tip of the vehicle they should use the
229     * following:
230     * <code>MutablePoint p = MutablePoint.valueOf(vector).plus(vector.getOrigin());</code>
231     *
232     * @param vector the GeomVector stated in length or dimensionless units. If in
233     *               dimensionless units, the values are interpreted as being in meters.
234     *               May not be null.
235     * @return the point having the specified values of the vector end point or tip.
236     * @throws ConversionException if the input vector is not in length (or Dimensionless)
237     * units.
238     */
239    public static MutablePoint valueOf(GeomVector<?> vector) {
240        MutablePoint P = FACTORY.object();
241        P._point = Point.valueOf(requireNonNull(vector));
242        return P;
243    }
244
245    /**
246     * Returns a {@link MutablePoint} instance containing the specified GeomPoint's data.
247     *
248     * @param point the GeomPoint to be copied into a new Point. May not be null.
249     * @return the point having the specified values.
250     */
251    public static MutablePoint valueOf(GeomPoint point) {
252        MutablePoint P = FACTORY.object();
253        P._point = point.immutable();
254        return P;
255    }
256
257    /**
258     * Recycles a <code>MutablePoint</code> instance immediately (on the stack when
259     * executing in a <code>StackContext</code>).
260     *
261     * @param instance The instance to be recycled.
262     */
263    public static void recycle(MutablePoint instance) {
264        FACTORY.recycle(instance);
265    }
266
267    /**
268     * Returns the number of physical dimensions of the geometry element.
269     *
270     * @return The number of physical dimensions of the geometry element.
271     */
272    @Override
273    public int getPhyDimension() {
274        return _point.getPhyDimension();
275    }
276
277    /**
278     * Set the value of elements of this point to the elements of the specified point. If
279     * the input GeomPoint is of type "Point", then the data stored in this MutablePoint
280     * is set to the data stored in "p" without creating any new object instances (copy by
281     * value).
282     *
283     * @param p The new point to make this point equal to. May not be null.
284     * @throws DimensionException <code>(p.getPhyDimension() != getPhyDimension())</code>
285     */
286    public void set(GeomPoint p) {
287        if (p.getPhyDimension() != getPhyDimension())
288            throw new DimensionException(RESOURCES.getString("consistantPointDim"));
289        _point = p.immutable();
290        fireChangeEvent();  //  Notify change listeners.
291    }
292
293    /**
294     * Set the value of elements of this point to the elements of the specified vector of
295     * Parameter objects. The origin of the vector is ignored.
296     *
297     * @param vector The new vector to make this point equal to in length or dimensionless
298     *               units. If in dimensionless units, the values are interpreted as being
299     *               in the same units as this point. May not be null.
300     * @throws DimensionException <code>(vector.getDimension() != getPhyDimension())</code>
301     * @throws ConversionException if the input vector is not in length (or Dimensionless)
302     * units.
303     */
304    public void set(GeomVector<?> vector) {
305        if (vector.getPhyDimension() != getPhyDimension())
306            throw new DimensionException(RESOURCES.getString("consistantPointDim"));
307        _point = Point.valueOf(vector);
308    }
309
310    /**
311     * Set the value of a point dimension to the specified Parameter.
312     *
313     * @param i     the dimension index.
314     * @param value The new value of the parameter to set at <code>i</code>. May not be
315     *              null.
316     * @throws IndexOutOfBoundsException <code>(i &lt; 0) || (i &ge; getPhyDimension())</code>
317     */
318    public void set(int i, Parameter<Length> value) {
319        requireNonNull(value);
320        
321        //  Get a list of values in this point.
322        FastTable<Parameter<Length>> values = FastTable.newInstance();
323        int size = _point.getPhyDimension();
324        for (int j = 0; j < size; ++j) {
325            values.add(_point.get(j));
326        }
327
328        //  Change the dimension indicated.
329        values.set(i, value);
330        _point = Point.valueOf(values);
331        FastTable.recycle(values);
332
333        fireChangeEvent();  //  Notify change listeners.
334    }
335
336    /**
337     * Set the value of a point dimension to the specified double in the current point
338     * units.
339     *
340     * @param i     the dimension index.
341     * @param value The new value of the parameter to set at <code>i</code> in the current
342     *              units of this point.
343     * @throws IndexOutOfBoundsException <code>(i &lt; 0) || (i &ge; getPhyDimension())</code>
344     */
345    public void setValue(int i, double value) {
346        //  Create a Parameter object.
347        Parameter<Length> param = Parameter.valueOf(value, getUnit());
348
349        //  Set the specified dimension to the new parameter object.
350        set(i, param);
351    }
352
353    /**
354     * Returns the value of a coordinate in this point as a <code>double</code>, stated in
355     * this point's {@link #getUnit unit}.
356     *
357     * @param i the dimension index.
358     * @return the value of the Parameter at <code>i</code>.
359     * @throws IndexOutOfBoundsException <code>(i &lt; 0) || (i &ge; getPhyDimension())</code>
360     */
361    @Override
362    public double getValue(int i) {
363        return _point.getValue(i);
364    }
365
366    /**
367     * Returns the value of a coordinate in this point as a <code>double</code>, stated in
368     * the specified unit.
369     *
370     * @param i    the dimension index.
371     * @param unit the unit to return the value in. May not be null.
372     * @return the value of the Parameter at <code>i</code> in the specified unit.
373     * @throws IndexOutOfBoundsException <code>(i &lt; 0) || (i &ge; getPhyDimension())</code>
374     */
375    @Override
376    public double getValue(int i, Unit<Length> unit) {
377        return _point.getValue(i, unit);
378    }
379
380    /**
381     * Returns the square of the Euclidean norm, magnitude, or length value of the vector
382     * from the origin to this point (the dot product of the origin-to-this-point vector
383     * and itself). This is slightly faster than calling <code>normValue</code> if the
384     * squared value is all that is needed.
385     *
386     * @return <code>this.normSq().getValue()</code>.
387     * @see #normValue() 
388     */
389    @Override
390    public double normSqValue() {
391        return _point.normSqValue();
392    }
393
394    /**
395     * Returns the sum of this point with the one specified. The unit of the output point
396     * will be the units of this point.
397     *
398     * @param that the point to be added. May not be null.
399     * @return <code>this + that</code>.
400     * @throws DimensionException if point dimensions are different.
401     */
402    @Override
403    public Point plus(GeomPoint that) {
404        return _point.plus(that);
405    }
406
407    /**
408     * Adds the specified parameter to each component of this point. The unit of the
409     * output point will be the units of this point.
410     *
411     * @param that the parameter to be added to each component of this point. May not be
412     *             null.
413     * @return <code>this + that</code>.
414     */
415    @Override
416    public Point plus(Parameter<Length> that) {
417        return _point.plus(that);
418    }
419
420    /**
421     * Returns the difference between this point and the one specified. The unit of the
422     * output point will be the units of this point.
423     *
424     * @param that the point to be subtracted from this point. May not be null.
425     * @return <code>this - that</code>.
426     * @throws DimensionException if point dimensions are different.
427     */
428    @Override
429    public Point minus(GeomPoint that) {
430        return _point.minus(that);
431    }
432
433    /**
434     * Subtracts the specified parameter from each component of this point. The unit of
435     * the output point will be the units of this point.
436     *
437     * @param that the parameter to be subtracted from each component of this point. May
438     *             not be null.
439     * @return <code>this - that</code>.
440     */
441    @Override
442    public Point minus(Parameter<Length> that) {
443        return _point.minus(that);
444    }
445
446    /**
447     * Returns the negation of this point (all the values of each dimension negated).
448     *
449     * @return <code>-this</code>
450     */
451    @Override
452    public Point opposite() {
453        return _point.opposite();
454    }
455
456    /**
457     * Returns the product of this point with the specified coefficient.
458     *
459     * @param k the coefficient multiplier.
460     * @return <code>this ยท k</code>
461     */
462    @Override
463    public Point times(double k) {
464        return _point.times(k);
465    }
466
467    /**
468     * Returns a copy of this MutablePoint instance
469     * {@link javolution.context.AllocatorContext allocated} by the calling thread
470     * (possibly on the stack).
471     *
472     * @return an identical and independent copy of this point.
473     */
474    @Override
475    public MutablePoint copy() {
476        return copyOf(this);
477    }
478
479    /**
480     * Return a copy of this object with any transformations or subranges removed
481     * (applied).
482     *
483     * @return A copy of this object with any transformations or subranges removed.
484     */
485    @Override
486    public Point copyToReal() {
487        return Point.valueOf(_point);
488    }
489
490    /**
491     * Returns the unit in which the {@link #getValue values} in this point are stated in.
492     *
493     * @return The unit in which the {@link #getValue values} in this point are stated in.
494     */
495    @Override
496    public final Unit<Length> getUnit() {
497        return _point.getUnit();
498    }
499
500    /**
501     * Return an immutable version of this point.
502     *
503     * @return An immutable version of this point.
504     */
505    @Override
506    public Point immutable() {
507        return _point;
508    }
509
510    /**
511     * Returns the equivalent to this point but stated in the specified unit.
512     *
513     * @param unit the length unit of the point to be returned. May not be null.
514     * @return an equivalent of this point but stated in the specified unit.
515     * @throws ConversionException if the the input unit is not a length unit.
516     */
517    @Override
518    public MutablePoint to(Unit<Length> unit) throws ConversionException {
519        if (unit.equals(getUnit()))
520            return this;
521        MutablePoint P = FACTORY.object();
522        P._point = _point.to(unit);
523        return P;
524    }
525
526    /**
527     * Return the equivalent of this point converted to the specified number of physical
528     * dimensions. If the number of dimensions is greater than this element, then zeros
529     * are added to the additional dimensions. If the number of dimensions is less than
530     * this element, then the extra dimensions are simply dropped (truncated). If the new
531     * dimensions are the same as the dimension of this element, then this element is
532     * simply returned.
533     *
534     * @param newDim The dimension of the point to return.
535     * @return A copy of this point converted to the new dimensions.
536     */
537    @Override
538    public MutablePoint toDimension(int newDim) {
539        if (newDim < 1)
540            throw new IllegalArgumentException(RESOURCES.getString("pointDimGT0"));
541        int thisDim = this.getPhyDimension();
542        if (newDim == thisDim)
543            return this;
544        MutablePoint P = FACTORY.object();
545        P._point = _point.toDimension(newDim);
546        return P;
547    }
548
549    /**
550     * Returns the values stored in this point, stated in this point's
551     * {@link #getUnit unit}, as a Float64Vector.
552     *
553     * @return A Float64Vector containing the coordinate values for this point.
554     */
555    @Override
556    public final Float64Vector toFloat64Vector() {
557        return _point.toFloat64Vector();
558    }
559
560    /**
561     * Compares this Point against the specified object for strict equality (same values
562     * and same units).
563     *
564     * @param obj the object to compare with.
565     * @return <code>true</code> if this point is identical to that point;
566     *         <code>false</code> otherwise.
567     */
568    @Override
569    public boolean equals(Object obj) {
570        if (this == obj)
571            return true;
572        if ((obj == null) || (obj.getClass() != this.getClass()))
573            return false;
574
575        MutablePoint that = (MutablePoint)obj;
576        return this._point.equals(that._point)
577                && super.equals(obj);
578    }
579
580    /**
581     * Returns the hash code for this parameter.
582     *
583     * @return the hash code value.
584     */
585    @Override
586    public int hashCode() {
587        return 31*super.hashCode() + Objects.hash(_point);
588    }
589
590    /**
591     * Holds the default XML representation for this object.
592     */
593    @SuppressWarnings("FieldNameHidesFieldInSuperclass")
594    protected static final XMLFormat<MutablePoint> XML = new XMLFormat<MutablePoint>(MutablePoint.class) {
595
596        @Override
597        public MutablePoint newInstance(Class<MutablePoint> cls, InputElement xml) throws XMLStreamException {
598            return FACTORY.object();
599        }
600
601        @Override
602        public void read(InputElement xml, MutablePoint obj) throws XMLStreamException {
603            Unit unit = Unit.valueOf(xml.getAttribute("unit"));
604            if (!Length.UNIT.isCompatible(unit))
605                throw new XMLStreamException(
606                        MessageFormat.format(RESOURCES.getString("incompatibleUnits"),
607                                "MutablePoint", "length"));
608
609            GeomPoint.XML.read(xml, obj);     // Call parent read.
610
611            FastTable<Float64> valueList = FastTable.newInstance();
612            while (xml.hasNext()) {
613                Float64 value = xml.getNext();
614                valueList.add(value);
615            }
616            obj._point = Point.valueOf(Float64Vector.valueOf(valueList), unit);
617
618        }
619
620        @Override
621        public void write(MutablePoint obj, OutputElement xml) throws XMLStreamException {
622            xml.setAttribute("unit", obj.getUnit().toString());
623
624            GeomPoint.XML.write(obj, xml);    // Call parent write.
625
626            int size = obj._point.getPhyDimension();
627            for (int i = 0; i < size; ++i)
628                xml.add(Float64.valueOf(obj._point.getValue(i)));
629
630        }
631    };
632
633    ///////////////////////
634    // Factory creation. //
635    ///////////////////////
636    private MutablePoint() { }
637
638    @SuppressWarnings("unchecked")
639    private static final ObjectFactory<MutablePoint> FACTORY = new ObjectFactory<MutablePoint>() {
640        @Override
641        protected MutablePoint create() {
642            return new MutablePoint();
643        }
644
645        @Override
646        protected void cleanup(MutablePoint obj) {
647            obj.reset();
648        }
649    };
650
651    @SuppressWarnings("unchecked")
652    private static MutablePoint copyOf(MutablePoint original) {
653        MutablePoint P = FACTORY.object();
654        P._point = original._point.copy();
655        original.copyState(P);
656        return P;
657    }
658
659}