001/*
002 *   LineSegTrans  -- A GeomTransform that has a LineSegment for a child.
003 *
004 *   Copyright (C) 2012-2025, by 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 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 *   Library 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;
023
024import geomss.geom.nurbs.NurbsCurve;
025import geomss.geom.nurbs.NurbsCurveTrans;
026import jahuwaldt.js.param.Parameter;
027import java.text.MessageFormat;
028import java.util.List;
029import java.util.Objects;
030import static java.util.Objects.requireNonNull;
031import javax.measure.converter.ConversionException;
032import javax.measure.quantity.Dimensionless;
033import javax.measure.quantity.Length;
034import javax.measure.unit.Unit;
035import javax.swing.event.ChangeListener;
036import javolution.context.ObjectFactory;
037import javolution.lang.Immutable;
038import javolution.xml.XMLFormat;
039import javolution.xml.stream.XMLStreamException;
040
041/**
042 * A {@link GeomTransform} object that refers to a {@link LineSegment} object
043 * and masquerades as a LineSegment object itself.
044 *
045 * <p>Modified by: Joseph A. Huwaldt</p>
046 *
047 * @author Joseph A. Huwaldt, Date: January 15, 2012
048 * @version February 17, 2025
049 */
050@SuppressWarnings({"serial", "CloneableImplementsClone"})
051public final class LineSegTrans extends LineSegment implements GeomTransform<LineSegment> {
052
053    /**
054     * The transformation represented by this transformation element.
055     */
056    private GTransform _TM;
057
058    /**
059     * The object that is the child of this transform.
060     */
061    private LineSegment _child;
062
063    /**
064     * Reference to a change listener for this object's child.
065     */
066    private ChangeListener _childChangeListener = new ForwardingChangeListener(this);
067
068    /**
069     * Returns a 3D {@link LineSegTrans} instance holding the specified
070     * {@link LineSegment} and {@link GTransform}.
071     *
072     * @param child The LineSegment that is the child of this transform element
073     *      (may not be <code>null</code>).
074     * @param transform The transform held by this transform element (may not be
075     *      <code>null</code>).
076     * @return the transform element having the specified values.
077     */
078    public static LineSegTrans newInstance(LineSegment child, GTransform transform) {
079        requireNonNull(child, MessageFormat.format(RESOURCES.getString("paramNullErr"), "child"));
080        requireNonNull(transform, MessageFormat.format(RESOURCES.getString("paramNullErr"), "transform"));
081
082        if (child.getPhyDimension() != 3)
083            throw new DimensionException(
084                    MessageFormat.format(RESOURCES.getString("dimensionNot3"),
085                        "line segment", String.valueOf(child.getPhyDimension())));
086
087        LineSegTrans obj = FACTORY.object();
088        obj._TM = transform;
089        obj._child = child;
090        child.copyState(obj);
091
092        //  Listen for changes to the child object and pass them on.
093        if (!(child instanceof Immutable))
094            child.addChangeListener(obj._childChangeListener);
095
096        return obj;
097    }
098
099    /**
100     * Returns the transformation represented by this transformation element.
101     *
102     * @return The transformation from the child object represented by this
103     *      element.
104     */
105    @Override
106    public GTransform getTransform() {
107        return _TM;
108    }
109
110    /**
111     * Returns the total transformation represented by an entire chain of
112     * GeomTransform objects below this one.
113     *
114     * @return The total transformation represented by the entire chain of
115     *      objects below this one.
116     */
117    @Override
118    public GTransform getTotalTransform() {
119        return GeomUtil.getTotalTransform(this);
120    }
121
122    /**
123     * Sets the transformation represented by this transformation element.
124     *
125     * @param transform The transform to set this transform element to (may not
126     *      be <code>null</code>).
127     */
128    @Override
129    public void setTransform(GTransform transform) {
130        requireNonNull(transform, MessageFormat.format(RESOURCES.getString("paramNullErr"), "transform"));
131        _TM = transform;
132        fireChangeEvent();
133    }
134
135    /**
136     * Returns the child object transformed by this transform element.
137     *
138     * @return The child object transformed by this transform element.
139     */
140    @Override
141    public LineSegment getChild() {
142        return _child;
143    }
144
145    /**
146     * Return a copy of the child object transformed by this transformation.
147     *
148     * @return A copy of the child object transformed by this transformation.
149     */
150    @Override
151    public LineSegment copyToReal() {
152        Point p0 = getStart().copyToReal();
153        Point p1 = getEnd().copyToReal();
154        LineSeg L = LineSeg.valueOf(p0, p1);
155        return copyState(L);    //  Copy over the super-class state for this object to the new one.
156    }
157
158    /**
159     * Return the starting point of the line segment.
160     *
161     * @return The starting point of the line segment.
162     */
163    @Override
164    public GeomPoint getStart() {
165        return _child.getStart().getTransformed(_TM);
166    }
167
168    /**
169     * Return the end point of the line segment.
170     *
171     * @return The end point of the lien segment.
172     */
173    @Override
174    public GeomPoint getEnd() {
175        return _child.getEnd().getTransformed(_TM);
176    }
177
178    /**
179     * Return the dimensional derivative vector for this line segment. The
180     * length of this vector is the length of the line segment, the origin is at
181     * the start point and the end of the vector is the line end.
182     *
183     * @return The dimensional derivative vector for this line segment.
184     */
185    @Override
186    public GeomVector<Length> getDerivativeVector() {
187        return _child.getDerivativeVector().getTransformed(_TM);
188    }
189
190    /**
191     * Get unit direction vector for the line segment.
192     *
193     * @return A unit vector indicating the direction of this line segment.
194     */
195    @Override
196    public GeomVector<Dimensionless> getUnitVector() {
197        return _child.getUnitVector().getTransformed(_TM);
198    }
199
200    /**
201     * Returns the number of child-elements that make up this geometry element.
202     * This implementation returns the size in the child curve.
203     */
204    @Override
205    public int size() {
206        return _child.size();
207    }
208
209    /**
210     * Returns the number of physical dimensions of the geometry element.
211     */
212    @Override
213    public int getPhyDimension() {
214        return _child.getPhyDimension();
215    }
216
217    /**
218     * Calculate a point on the curve for the given parametric distance along
219     * the curve, <code>p(s)</code>.
220     *
221     * @param s parametric distance to calculate a point for (0.0 to 1.0
222     *      inclusive).
223     * @return the calculated point
224     */
225    @Override
226    public Point getRealPoint(double s) {
227        return _TM.transform(_child.getRealPoint(s));
228    }
229
230    /**
231     * Calculate all the derivatives from <code>0</code> to <code>grade</code>
232     * with respect to parametric distance on the curve for the given parametric
233     * distance along the curve, <code>d^{grade}p(s)/d^{grade}s</code>. 
234     * <p>
235     * Example:<br>
236     * 1st derivative (grade = 1), this returns <code>[p(s), dp(s)/ds]</code>;<br>
237     * 2nd derivative (grade = 2), this returns <code>[p(s), dp(s)/ds, d^2p(s)/d^2s]</code>; etc.
238     * </p>
239     *
240     * @param s Parametric distance to calculate derivatives for (0.0 to 1.0
241     *      inclusive).
242     * @param grade The maximum grade to calculate the derivatives for (1=1st
243     *      derivative, 2=2nd derivative, etc)
244     * @return A list of derivatives up to the specified grade of the curve at
245     *      the specified parametric position.
246     * @throws IllegalArgumentException if the grade is &lt; 0.
247     */
248    @Override
249    public List<Vector<Length>> getSDerivatives(double s, int grade) {
250        LineSegment trans = copyToReal();
251        return trans.getSDerivatives(s, grade);
252    }
253
254    /**
255     * Return a new curve that is identical to this one, but with the
256     * parameterization reversed.
257     *
258     * @return A new curve that is identical to this one, but with the
259     * parameterization reversed.
260     */
261    @Override
262    public LineSegTrans reverse() {
263        return LineSegTrans.newInstance(_child.reverse(), _TM);
264    }
265
266    /**
267     * Return the coordinate point representing the minimum bounding box corner
268     * of this geometry element (e.g.: min X, min Y, min Z).
269     *
270     * @return The minimum bounding box coordinate for this geometry element.
271     * @throws IndexOutOfBoundsException if this list contains no elements.
272     */
273    @Override
274    public Point getBoundsMin() {
275        LineSegment trans = copyToReal();
276        Point minPoint = trans.getBoundsMin();
277        return minPoint;
278    }
279
280    /**
281     * Return the coordinate point representing the maximum bounding box corner
282     * (e.g.: max X, max Y, max Z).
283     *
284     * @return The maximum bounding box coordinate for this geometry element.
285     * @throws IndexOutOfBoundsException if this list contains no elements.
286     */
287    @Override
288    public Point getBoundsMax() {
289        LineSegment trans = copyToReal();
290        Point maxPoint = trans.getBoundsMax();
291        return maxPoint;
292    }
293
294    /**
295     * Returns transformed version of this element. The returned object
296     * implements {@link GeomTransform} and contains this element as a child.
297     *
298     * @param transform The transformation to apply to this geometry. May not be null.
299     * @return A new line segment that is identical to this one with the
300     *      specified transformation applied.
301     * @throws DimensionException if this point is not 3D.
302     */
303    @Override
304    public LineSegTrans getTransformed(GTransform transform) {
305        return LineSegTrans.newInstance(this, requireNonNull(transform));
306    }
307
308    /**
309     * Returns the unit in which this curve is stated.
310     */
311    @Override
312    public Unit<Length> getUnit() {
313        return _child.getUnit();
314    }
315
316    /**
317     * Returns the equivalent to this curve but stated in the specified unit.
318     *
319     * @param unit the length unit of the curve to be returned. May not be null.
320     * @return an equivalent to this curve but stated in the specified unit.
321     * @throws ConversionException if the the input unit is not a length unit.
322     */
323    @Override
324    public LineSegTrans to(Unit<Length> unit) throws ConversionException {
325        if (unit.equals(getUnit()))
326            return this;
327
328        return LineSegTrans.newInstance(_child.to(unit), _TM);
329    }
330
331    /**
332     * Return the equivalent of this line segment converted to the specified
333     * number of physical dimensions. This implementation will throw an
334     * exception if the specified dimension is anything other than 3.
335     *
336     * @param newDim The dimension of the line segment to return. MUST equal 3.
337     * @return The equivalent of this line segment converted to the new
338     *      dimensions.
339     * @throws IllegalArgumentException if the new dimension is anything other
340     *      than 3.
341     */
342    @Override
343    public LineSegTrans toDimension(int newDim) {
344        if (newDim == 3)
345            return this;
346
347        throw new IllegalArgumentException(
348                MessageFormat.format(RESOURCES.getString("dimensionNot3_2"), this.getClass().getName()));
349    }
350
351    /**
352     * Return a transformed NURBS curve representation of this line segment. An
353     * exact representation is returned and the tolerance parameter is ignored.
354     *
355     * @param tol Ignored. May pass <code>null</code>.
356     * @return A transformed NURBS curve that exactly represents this line
357     * segment.
358     */
359    @Override
360    public NurbsCurve toNurbs(Parameter<Length> tol) {
361        NurbsCurve curve = NurbsCurveTrans.newInstance(_child.toNurbs(tol), _TM);
362        copyState(curve);   //  Copy over the super-class state for this curve to the new one.
363        return curve;
364    }
365
366    /**
367     * Returns a copy of this LineSegTrans instance allocated by the calling
368     * thread (possibly on the stack).
369     *
370     * @return an identical and independent copy of this point.
371     */
372    @Override
373    public LineSegTrans copy() {
374        return LineSegTrans.copyOf(this);
375    }
376
377    /**
378     * Compares this LineSegTrans against the specified object for strict
379     * equality.
380     *
381     * @param obj the object to compare with.
382     * @return <code>true</code> if this transform is identical to that
383     *      transform; <code>false</code> otherwise.
384     */
385    @Override
386    public boolean equals(Object obj) {
387        if (this == obj)
388            return true;
389        if ((obj == null) || (obj.getClass() != this.getClass()))
390            return false;
391
392        LineSegTrans that = (LineSegTrans)obj;
393        return this._TM.equals(that._TM)
394                && this._child.equals(that._child)
395                && super.equals(obj);
396    }
397
398    /**
399     * Returns the hash code for this parameter.
400     *
401     * @return the hash code value.
402     */
403    @Override
404    public int hashCode() {
405        return 31*super.hashCode() + Objects.hash(_TM, _child);
406    }
407
408    /**
409     * Holds the default XML representation for this object.
410     */
411    @SuppressWarnings("FieldNameHidesFieldInSuperclass")
412    protected static final XMLFormat<LineSegTrans> XML = new XMLFormat<LineSegTrans>(LineSegTrans.class) {
413
414        @Override
415        public LineSegTrans newInstance(Class<LineSegTrans> cls, InputElement xml) throws XMLStreamException {
416            return FACTORY.object();
417        }
418
419        @Override
420        public void read(InputElement xml, LineSegTrans obj) throws XMLStreamException {
421            LineSegment.XML.read(xml, obj);     // Call parent read.
422
423            obj._TM = xml.getNext();
424            LineSegment child = xml.getNext();
425            obj._child = xml.getNext();
426
427            //  Listen for changes to the child object and pass them on.
428            if (!(child instanceof Immutable))
429                child.addChangeListener(obj._childChangeListener);
430        }
431
432        @Override
433        public void write(LineSegTrans obj, OutputElement xml) throws XMLStreamException {
434            LineSegment.XML.write(obj, xml);    // Call parent write.
435
436            xml.add(obj._TM);
437            xml.add(obj._child);
438
439        }
440    };
441
442    //////////////////////
443    // Factory Creation //
444    //////////////////////
445    private static final ObjectFactory<LineSegTrans> FACTORY = new ObjectFactory<LineSegTrans>() {
446        @Override
447        protected LineSegTrans create() {
448            return new LineSegTrans();
449        }
450
451        @Override
452        protected void cleanup(LineSegTrans obj) {
453            obj.reset();
454            obj._TM = null;
455            if (!(obj._child instanceof Immutable))
456                obj._child.removeChangeListener(obj._childChangeListener);
457            obj._child = null;
458        }
459    };
460
461    /**
462     * Recycles a <code>LineSegTrans</code> instance immediately (on the stack
463     * when executing in a StackContext).
464     *
465     * @param instance The instance to recycle immediately.
466     */
467    public static void recycle(LineSegTrans instance) {
468        FACTORY.recycle(instance);
469    }
470
471    @SuppressWarnings("unchecked")
472    private static LineSegTrans copyOf(LineSegTrans original) {
473        LineSegTrans obj = FACTORY.object();
474        obj._TM = original._TM.copy();
475        obj._child = original._child.copy();
476        original.copyState(obj);
477        if (!(obj._child instanceof Immutable))
478            obj._child.addChangeListener(obj._childChangeListener);
479        return obj;
480    }
481
482    /**
483     * Do not allow the default constructor to be used except by subclasses.
484     */
485    private LineSegTrans() {}
486
487}