1 /*
2  * Copyright 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 @file:Suppress("NOTHING_TO_INLINE")
18 
19 package androidx.xr.runtime.math
20 
21 import kotlin.math.abs
22 import kotlin.math.acos
23 import kotlin.math.max
24 import kotlin.math.min
25 import kotlin.math.sqrt
26 
27 /**
28  * Represents a position in the 2D plane.
29  *
30  * @property x X component of the vector.
31  * @property y Y component of the vector.
32  */
33 public class Vector2 @JvmOverloads constructor(public val x: Float = 0F, public val y: Float = 0F) {
34     /** The squared length of the vector. */
35     public inline val lengthSquared: Float
36         get() = x * x + y * y
37 
38     /** The length of the vector. */
39     public inline val length: Float
40         get() = sqrt(lengthSquared)
41 
42     /** Creates a new vector with the same values as the [other] vector. */
43     public constructor(other: Vector2) : this(other.x, other.y)
44 
45     /** Negates the values of this vector. */
unaryMinusnull46     public inline operator fun unaryMinus(): Vector2 = Vector2(-x, -y)
47 
48     /** Returns a new vector with the sum of this vector and the [other] vector. */
49     public operator fun plus(other: Vector2): Vector2 = Vector2(this.x + other.x, this.y + other.y)
50 
51     /** Returns a new vector with the difference of this vector and the [other] vector. */
52     public inline operator fun minus(other: Vector2): Vector2 =
53         Vector2(this.x - other.x, this.y - other.y)
54 
55     /** Returns a new vector multiplied by a scalar amount */
56     public inline operator fun times(c: Float): Vector2 = Vector2(x * c, y * c)
57 
58     /** Returns a new vector with the product of this vector and the [other] vector. */
59     public inline operator fun times(other: Vector2): Vector2 =
60         Vector2(this.x * other.x, this.y * other.y)
61 
62     /** Returns a new vector with this vector divided by a scalar amount. */
63     public inline operator fun div(c: Float): Vector2 = Vector2(x / c, y / c)
64 
65     /** Returns a new vector with this vector divided by the [other] vector. */
66     public inline operator fun div(other: Vector2): Vector2 = Vector2(x / other.x, y / other.y)
67 
68     /** Returns a normalized version of this vector. */
69     public fun toNormalized(): Vector2 {
70         val norm = rsqrt(lengthSquared)
71 
72         return Vector2(x * norm, y * norm)
73     }
74 
75     /** Returns the cross product of this vector and the [other] vector. */
crossnull76     public inline infix fun cross(other: Vector2): Float = this.x * other.y - this.y * other.x
77 
78     /** Returns the dot product of this vector and the [other] vector. */
79     public inline infix fun dot(other: Vector2): Float = x * other.x + y * other.y
80 
81     /** Returns a new vector with the values clamped between [min] and [max] vectors. */
82     public fun clamp(min: Vector2, max: Vector2): Vector2 {
83         var clampedX = max(x, min.x)
84         var clampedY = max(y, min.y)
85 
86         clampedX = min(clampedX, max.x)
87         clampedY = min(clampedY, max.y)
88 
89         return Vector2(clampedX, clampedY)
90     }
91 
92     /** Returns a copy of the vector. */
93     @JvmOverloads
copynull94     public inline fun copy(x: Float = this.x, y: Float = this.y): Vector2 = Vector2(x, y)
95 
96     /** Returns true if this vector is equal to the [other]. */
97     override fun equals(other: Any?): Boolean {
98         if (this === other) return true
99         if (other !is Vector2) return false
100 
101         return this.x == other.x && this.y == other.y
102     }
103 
hashCodenull104     override fun hashCode(): Int = 31 * x.hashCode() + y.hashCode()
105 
106     override fun toString(): String = "[x=$x, y=$y]"
107 
108     public companion object {
109         /** Vector with all components set to zero. */
110         @JvmField public val Zero: Vector2 = Vector2(x = 0f, y = 0f)
111 
112         /** Vector with all components set to one. */
113         @JvmField public val One: Vector2 = Vector2(x = 1f, y = 1f)
114 
115         /** Vector with y set to one and all other components set to zero. */
116         @JvmField public val Up: Vector2 = Vector2(x = 0f, y = 1f)
117 
118         /** Vector with y set to negative one and all other components set to zero. */
119         @JvmField public val Down: Vector2 = Vector2(x = 0f, y = -1f)
120 
121         /** Vector with x set to negative one and all other components set to zero. */
122         @JvmField public val Left: Vector2 = Vector2(x = -1f, y = 0f)
123 
124         /** Vector with x set to one and all other components set to zero. */
125         @JvmField public val Right: Vector2 = Vector2(x = 1f, y = 0f)
126 
127         /** Returns the distance between this vector and the [other] vector. */
128         @JvmStatic
129         public fun distance(vector1: Vector2, vector2: Vector2): Float = (vector1 - vector2).length
130 
131         /** Returns the angle between this vector and the [other] vector. */
132         @JvmStatic
133         public fun angularDistance(vector1: Vector2, vector2: Vector2): Float {
134             val dot = vector1 dot vector2
135             val magnitude = vector1.length * vector2.length
136 
137             if (magnitude < 1e-10f) {
138                 return 0.0f
139             }
140 
141             // Clamp due to floating point precision errors that could cause dot to be > mag.
142             // Would cause acos to return NaN.
143             val cos = clamp(dot / magnitude, -1.0f, 1.0f)
144             val angleRadians = acos(cos)
145 
146             return toDegrees(angleRadians)
147         }
148 
149         /**
150          * Returns a new vector that is linearly interpolated between [start] and [end] using the
151          * interpolation amount [ratio].
152          *
153          * If [ratio] is outside of the range `[0, 1]`, the returned vector will be extrapolated.
154          */
155         @JvmStatic
156         public fun lerp(start: Vector2, end: Vector2, ratio: Float): Vector2 =
157             Vector2(lerp(start.x, end.x, ratio), lerp(start.y, end.y, ratio))
158 
159         /** Returns the absolute values of each component of the vector. */
160         @JvmStatic public fun abs(vector: Vector2): Vector2 = Vector2(abs(vector.x), abs(vector.y))
161     }
162 }
163