001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.numbers.angle;
018
019import java.util.function.DoubleSupplier;
020import java.util.function.DoubleUnaryOperator;
021
022/**
023 * Represents the <a href="https://en.wikipedia.org/wiki/Angle">angle</a> concept.
024 */
025public abstract class Angle implements DoubleSupplier {
026    /** 2&pi;. */
027    public static final double TWO_PI = 2 * Math.PI;
028    /** &pi;/2. */
029    public static final double PI_OVER_TWO = 0.5 * Math.PI;
030    /** Turns to degrees conversion factor. */
031    private static final double TURN_TO_DEG = 360d;
032    /** Radians to degrees conversion factor. */
033    private static final double RAD_TO_DEG = 180.0 / Math.PI;
034    /** Degrees to radians conversion factor. */
035    private static final double DEG_TO_RAD = Math.PI / 180.0;
036
037    /** Value (unit depends on concrete instance). */
038    private final double value;
039
040    /**
041     * @param value Value in turns.
042     */
043    Angle(final double value) {
044        this.value = value;
045    }
046
047    /** @return the value. */
048    @Override
049    public double getAsDouble() {
050        return value;
051    }
052
053    /** {@inheritDoc} */
054    @Override
055    public int hashCode() {
056        return Double.hashCode(value);
057    }
058
059    /**
060     * Test for equality with another object.
061     * Objects are considered to be equal if their concrete types are
062     * equal and their values are exactly the same (or both are {@code Double.NaN}).
063     *
064     * @param other Object to test for equality with this instance.
065     * @return {@code true} if the objects are equal, {@code false} if
066     * {@code other} is {@code null}, not of the same type as this instance,
067     * or not equal to this instance.
068     */
069    @Override
070    public boolean equals(final Object other) {
071        return other != null &&
072                getClass().equals(other.getClass()) &&
073                Double.doubleToLongBits(value) == Double.doubleToLongBits(((Angle) other).value);
074    }
075
076    /**
077     * Convert to a {@link Turn}.
078     *
079     * @return the angle in <a href="https://en.wikipedia.org/wiki/Turn_%28geometry%29">turns</a>.
080     */
081    public abstract Turn toTurn();
082
083    /**
084     * Convert to a {@link Rad}.
085     *
086     * @return the angle in <a href="https://en.wikipedia.org/wiki/Radian">radians</a>.
087     */
088    public abstract Rad toRad();
089
090    /**
091     * Convert to a {@link Deg}.
092     *
093     * @return the angle in <a href="https://en.wikipedia.org/wiki/Degree_%28angle%29">degrees</a>.
094     */
095    public abstract Deg toDeg();
096
097    /**
098     * Unit: <a href="https://en.wikipedia.org/wiki/Turn_%28geometry%29">turns</a>.
099     */
100    public static final class Turn extends Angle {
101        /** Zero. */
102        public static final Turn ZERO = of(0d);
103        /** Normalizing operator (result will be within the {@code [0, 1[} interval). */
104        public static final DoubleUnaryOperator WITHIN_0_AND_1 = normalizer(0d);
105
106        /**
107         * Create an instance.
108         *
109         * @param angle (in turns).
110         */
111        private Turn(final double angle) {
112            super(angle);
113        }
114
115        /**
116         * Create an instance.
117         *
118         * @param angle (in turns).
119         * @return a new instance.
120         */
121        public static Turn of(final double angle) {
122            return new Turn(angle);
123        }
124
125        /** {@inheritDoc} */
126        @Override
127        public Turn toTurn() {
128            return this;
129        }
130
131        /** {@inheritDoc} */
132        @Override
133        public Rad toRad() {
134            return Rad.of(getAsDouble() * TWO_PI);
135        }
136
137        /** {@inheritDoc} */
138        @Override
139        public Deg toDeg() {
140            return Deg.of(getAsDouble() * TURN_TO_DEG);
141        }
142
143        /**
144         * Creates an operator for normalizing/reducing an angle.
145         * The output will be within the {@code [lo, lo + 1[} interval.
146         *
147         * @param lo Lower bound of the normalized interval.
148         * @return the normalization operator.
149         */
150        public static DoubleUnaryOperator normalizer(final double lo) {
151            return new Normalizer(lo, 1d);
152        }
153    }
154
155    /**
156     * Unit: <a href="https://en.wikipedia.org/wiki/Radian">radians</a>.
157     */
158    public static final class Rad extends Angle {
159        /** Zero. */
160        public static final Rad ZERO = of(0d);
161        /** &pi;. */
162        public static final Rad PI = of(Math.PI);
163        /** 2&pi;. */
164        public static final Rad TWO_PI = of(Angle.TWO_PI);
165        /** Normalizing operator (result will be within the <code>[0, 2&pi;[</code> interval). */
166        public static final DoubleUnaryOperator WITHIN_0_AND_2PI = normalizer(0d);
167        /** Normalizing operator (result will be within the <code>[-&pi;, &pi;[</code> interval). */
168        public static final DoubleUnaryOperator WITHIN_MINUS_PI_AND_PI = normalizer(-Math.PI);
169
170        /**
171         * Create an instance.
172         *
173         * @param angle (in radians).
174         */
175        private Rad(final double angle) {
176            super(angle);
177        }
178
179        /**
180         * Create an instance.
181         *
182         * @param angle (in radians).
183         * @return a new instance.
184         */
185        public static Rad of(final double angle) {
186            return new Rad(angle);
187        }
188
189        /** {@inheritDoc} */
190        @Override
191        public Turn toTurn() {
192            return Turn.of(getAsDouble() / Angle.TWO_PI);
193        }
194
195        /** {@inheritDoc} */
196        @Override
197        public Rad toRad() {
198            return this;
199        }
200
201        /** {@inheritDoc} */
202        @Override
203        public Deg toDeg() {
204            return Deg.of(getAsDouble() * RAD_TO_DEG);
205        }
206
207        /**
208         * Creates an operator for normalizing/reducing an angle.
209         * The output will be within the <code> [lo, lo + 2&pi;[</code> interval.
210         *
211         * @param lo Lower bound of the normalized interval.
212         * @return the normalization operator.
213         */
214        public static DoubleUnaryOperator normalizer(final double lo) {
215            return new Normalizer(lo, Angle.TWO_PI);
216        }
217    }
218
219    /**
220     * Unit: <a href="https://en.wikipedia.org/wiki/Degree_%28angle%29">degrees</a>.
221     */
222    public static final class Deg extends Angle {
223        /** Zero. */
224        public static final Deg ZERO = of(0d);
225        /** Normalizing operator (result will be within the {@code [0, 360[} interval). */
226        public static final DoubleUnaryOperator WITHIN_0_AND_360 = normalizer(0d);
227
228        /**
229         * Create an instance.
230         *
231         * @param angle (in degrees).
232         */
233        private Deg(final double angle) {
234            super(angle);
235        }
236
237        /**
238         * Create an instance.
239         *
240         * @param angle (in degrees).
241         * @return a new instance.
242         */
243        public static Deg of(final double angle) {
244            return new Deg(angle);
245        }
246
247        /** {@inheritDoc} */
248        @Override
249        public Turn toTurn() {
250            return Turn.of(getAsDouble() / TURN_TO_DEG);
251        }
252
253        /** {@inheritDoc} */
254        @Override
255        public Rad toRad() {
256            return Rad.of(getAsDouble() * DEG_TO_RAD);
257        }
258
259        /** {@inheritDoc} */
260        @Override
261        public Deg toDeg() {
262            return this;
263        }
264
265        /**
266         * Creates an operator for normalizing/reducing an angle.
267         * The output will be within the {@code [c, c + 360[} interval.
268         *
269         * @param lo Lower bound of the normalized interval.
270         * @return the normalization operator.
271         */
272        public static DoubleUnaryOperator normalizer(final double lo) {
273            return new Normalizer(lo, TURN_TO_DEG);
274        }
275    }
276
277    /**
278     * Normalizes an angle around a center value.
279     */
280    private static final class Normalizer implements DoubleUnaryOperator {
281        /** Lower bound. */
282        private final double lo;
283        /** Upper bound. */
284        private final double hi;
285        /** Period. */
286        private final double period;
287        /** Normalizer. */
288        private final Reduce reduce;
289
290        /**
291         * Note: It is assumed that both arguments have the same unit.
292         *
293         * @param lo Lower bound of the desired interval.
294         * @param period Circonference of the circle.
295         */
296        Normalizer(final double lo,
297                   final double period) {
298            this.period = period;
299            this.lo = lo;
300            this.hi = lo + period;
301            reduce = new Reduce(lo, period);
302        }
303
304        /**
305         * @param a Angle.
306         * @return {@code = a - k} where {@code k} is an integer that satisfies
307         * {@code lo <= a - k < lo + period}.
308         */
309        @Override
310        public double applyAsDouble(final double a) {
311            if (lo <= a &&
312                a < hi) {
313                // Already within the main interval.
314                return a;
315            }
316
317            final double normalized = reduce.applyAsDouble(a) + lo;
318            return normalized < hi ?
319                normalized :
320                // If value is too small to be representable compared to the
321                // floor expression above (i.e. value + x = x), then we may
322                // end up with a number exactly equal to the upper bound.
323                // In that case, subtract one period from the normalized value
324                // so that the result is strictly less than the upper bound. (We also
325                // want to ensure that we do not return anything less than the lower bound.)
326                Math.max(lo, normalized - period);
327        }
328    }
329}