001/*
002 * Shredzone Commons - suncalc
003 *
004 * Copyright (C) 2017 Richard "Shred" Körber
005 *   http://commons.shredzone.org
006 *
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
013 */
014package org.shredzone.commons.suncalc.util;
015
016import static java.lang.Math.cos;
017import static java.lang.Math.sin;
018
019import java.util.Arrays;
020
021/**
022 * A three dimensional matrix.
023 * <p>
024 * Objects are immutable and threadsafe.
025 */
026public class Matrix {
027
028    private final double[] mx;
029
030    private Matrix() {
031        mx = new double[9];
032    }
033
034    private Matrix(double... values) {
035        if (values == null || values.length != 9) {
036            throw new IllegalArgumentException("requires 9 values");
037        }
038        mx = values;
039    }
040
041    /**
042     * Creates an identity matrix.
043     *
044     * @return Identity {@link Matrix}
045     */
046    public static Matrix identity() {
047        return new Matrix(
048            1.0, 0.0, 0.0,
049            0.0, 1.0, 0.0,
050            0.0, 0.0, 1.0);
051    }
052
053    /**
054     * Creates a matrix that rotates a vector by the given angle at the X axis.
055     *
056     * @param angle
057     *            angle, in radians
058     * @return Rotation {@link Matrix}
059     */
060    public static Matrix rotateX(double angle) {
061        double s = sin(angle);
062        double c = cos(angle);
063        return new Matrix(
064            1.0, 0.0, 0.0,
065            0.0,   c,   s,
066            0.0,  -s,   c
067        );
068    }
069
070    /**
071     * Creates a matrix that rotates a vector by the given angle at the Y axis.
072     *
073     * @param angle
074     *            angle, in radians
075     * @return Rotation {@link Matrix}
076     */
077    public static Matrix rotateY(double angle) {
078        double s = sin(angle);
079        double c = cos(angle);
080        return new Matrix(
081              c, 0.0,  -s,
082            0.0, 1.0, 0.0,
083              s, 0.0,   c
084        );
085    }
086
087    /**
088     * Creates a matrix that rotates a vector by the given angle at the Z axis.
089     *
090     * @param angle
091     *            angle, in radians
092     * @return Rotation {@link Matrix}
093     */
094    public static Matrix rotateZ(double angle) {
095        double s = sin(angle);
096        double c = cos(angle);
097        return new Matrix(
098              c,   s, 0.0,
099             -s,   c, 0.0,
100            0.0, 0.0, 1.0
101        );
102    }
103
104    /**
105     * Transposes this matrix.
106     *
107     * @return {@link Matrix} that is a transposition of this matrix.
108     */
109    public Matrix transpose() {
110        Matrix result = new Matrix();
111        for (int i = 0; i < 3; i++) {
112            for (int j = 0; j < 3; j++) {
113                result.set(i, j, get(j, i));
114            }
115        }
116        return result;
117    }
118
119    /**
120     * Negates this matrix.
121     *
122     * @return {@link Matrix} that is a negation of this matrix.
123     */
124    public Matrix negate() {
125        Matrix result = new Matrix();
126        for (int i = 0; i < 9; i++) {
127            result.mx[i] = -mx[i];
128        }
129        return result;
130    }
131
132    /**
133     * Adds a matrix to this matrix.
134     *
135     * @param right
136     *            {@link Matrix} to add
137     * @return {@link Matrix} that is a sum of both matrices
138     */
139    public Matrix add(Matrix right) {
140        Matrix result = new Matrix();
141        for (int i = 0; i < 9; i++) {
142            result.mx[i] = mx[i] + right.mx[i];
143        }
144        return result;
145    }
146
147    /**
148     * Subtracts a matrix from this matrix.
149     *
150     * @param right
151     *            {@link Matrix} to subtract
152     * @return {@link Matrix} that is the difference of both matrices
153     */
154    public Matrix subtract(Matrix right) {
155        Matrix result = new Matrix();
156        for (int i = 0; i < 9; i++) {
157            result.mx[i] = mx[i] - right.mx[i];
158        }
159        return result;
160    }
161
162    /**
163     * Multiplies two matrices.
164     *
165     * @param right
166     *            {@link Matrix} to multiply with
167     * @return {@link Matrix} that is the product of both matrices
168     */
169    public Matrix multiply(Matrix right) {
170        Matrix result = new Matrix();
171        for (int i = 0; i < 3; i++) {
172            for (int j = 0; j < 3; j++) {
173                double scalp = 0.0;
174                for (int k = 0; k < 3; k++) {
175                    scalp += get(i, k) * right.get(k, j);
176                }
177                result.set(i, j, scalp);
178            }
179        }
180        return result;
181    }
182
183    /**
184     * Performs a scalar multiplication.
185     *
186     * @param scalar
187     *            Scalar to multiply with
188     * @return {@link Matrix} that is the scalar product
189     */
190    public Matrix multiply(double scalar) {
191        Matrix result = new Matrix();
192        for (int i = 0; i < 9; i++) {
193            result.mx[i] = mx[i] * scalar;
194        }
195        return result;
196    }
197
198    /**
199     * Applies this matrix to a {@link Vector}.
200     *
201     * @param right
202     *            {@link Vector} to multiply with
203     * @return {@link Vector} that is the product of this matrix and the given vector
204     */
205    public Vector multiply(Vector right) {
206        double[] vec = new double[] {right.getX(), right.getY(), right.getZ()};
207        double[] result = new double[3];
208
209        for (int i = 0; i < 3; i++) {
210            double scalp = 0.0;
211            for (int j = 0; j < 3; j++) {
212                scalp += get(i, j) * vec[j];
213            }
214            result[i] = scalp;
215        }
216
217        return new Vector(result);
218    }
219
220    /**
221     * Gets a value from the matrix.
222     *
223     * @param r
224     *            Row number (0..2)
225     * @param c
226     *            Column number (0..2)
227     * @return Value at that position
228     */
229    public double get(int r, int c) {
230        if (r < 0 || r > 2 || c < 0 || c > 2) {
231            throw new IllegalArgumentException("row/column out of range: " + r + ":" + c);
232        }
233        return mx[r * 3 + c];
234    }
235
236    /**
237     * Changes a value in the matrix. As a {@link Matrix} object is immutable from the
238     * outside, this method is private.
239     *
240     * @param r
241     *            Row number (0..2)
242     * @param c
243     *            Column number (0..2)
244     * @param v
245     *            New value
246     */
247    private void set(int r, int c, double v) {
248        if (r < 0 || r > 2 || c < 0 || c > 2) {
249            throw new IllegalArgumentException("row/column out of range: " + r + ":" + c);
250        }
251        mx[r * 3 + c] = v;
252    }
253
254    @Override
255    public boolean equals(Object obj) {
256        if (obj == null || !(obj instanceof Matrix)) {
257            return false;
258        }
259        return Arrays.equals(mx, ((Matrix) obj).mx);
260    }
261
262    @Override
263    public int hashCode() {
264        return Arrays.hashCode(mx);
265    }
266
267    @Override
268    public String toString() {
269        StringBuilder sb = new StringBuilder();
270        sb.append('[');
271        for (int ix = 0; ix < 9; ix++) {
272            if (ix % 3 == 0) {
273                sb.append('[');
274            }
275            sb.append(mx[ix]);
276            if (ix % 3 == 2) {
277                sb.append(']');
278            }
279            if (ix < 8) {
280                sb.append(", ");
281            }
282        }
283        sb.append(']');
284        return sb.toString();
285    }
286
287}