1 /++
2 Module for working with mathematical angles. Contains the translation of
3 measurement systems, work in the form of determining the angle,
4 vector rotation and more.
5 
6 Macros:
7     LREF = <a href="#$1">$1</a>
8     HREF = <a href="$1">$2</a>
9 
10 Authors: $(HREF https://github.com/TodNaz,TodNaz)
11 Copyright: Copyright (c) 2020 - 2021, TodNaz.
12 License: $(HREF https://github.com/TodNaz/Tida/blob/master/LICENSE,MIT)
13 +/
14 module tida.angle;
15 
16 import std.math : PI;
17 
18 enum Radians = 0; /// Radians
19 enum Degrees = 1; /// Degrees
20 enum Turns = 2; /// Turns
21 enum Gons = 3; /// Gons
22 
23 /++
24 Maximum angle value.
25 
26 Params:
27     Type = Type angle.
28     
29 Example:
30 ---
31 auto maxRad = max!Radians;
32 ---
33 +/
34 template max(ubyte Type)
35 {
36     static if (Type == Radians) 
37     {
38         enum max = 2 * PI;
39     }else
40     static if (Type == Degrees)
41     {
42         enum max = 360.0f;
43     }else
44     static if (Type == Turns)
45     {
46         enum max = 1.0f;
47     }else
48     static if (Type == Gons)
49     {
50         enum max = 400.0f;
51     }
52 }
53 
54 alias perigon = max; /// perigon
55 
56 /++
57 Returns the right angle.
58 
59 Params:
60     Type = Type angle.
61     
62 Example:
63 ---
64 auto riaRad = rightAngle!Radians;
65 ---
66 +/
67 template rightAngle(ubyte Type)
68 {
69     static if (Type == Radians)
70     {
71         enum rightAngle = PI / 2;
72     }else
73     static if (Type == Degrees)
74     {
75         enum rightAngle = 90.0f;
76     }else
77     static if (Type == Turns)
78     {
79         enum rightAngle = 1 / 4;
80     }else
81     static if (Type == Gons)
82     {
83         enum rightAngle = 100;
84     }else
85         static assert(null, "Unknown angle type!");
86 }
87 
88 /++
89 Returns straight angle.
90 
91 Params:
92     Type = Type angle.
93     
94 Example:
95 ---
96 auto strRad = straight!Radians;
97 ---
98 +/
99 template straight(ubyte Type)
100 {   
101     static if (Type == Radians)
102     {
103         enum straight = PI;
104     }else
105     static if (Type == Degrees) 
106     {
107         enum straight =  180;
108     }else
109     static if (Type == Turns)
110     {
111         enum straight =  0.5;
112     }else
113     static if (Type == Gons)
114     {
115         enum straight =  200;
116     }else
117         static assert(null,"Unknown angle type!");
118 }
119 
120 /++
121 Translate one system of angles into another.
122 
123 Params:
124     What = What to translate.
125     In = What to translate.
126     value = Value.
127 
128 Example:
129 ---
130 from(Degrees,Radians)(45);
131 ---
132 +/
133 float conv(ubyte What,ubyte In)(float value) @safe nothrow pure
134 {
135     import std.math;
136     
137     static if (What == In) return value;
138     else
139     static if (In == Turns) return value / max!What;
140     else
141     static if (What == Turns) return max!In * value;
142     else
143     static if (What == Radians)
144     {
145         static if (In == Degrees)
146         {
147             return value * 180 / PI;
148         }else
149         static if (In == Gons)
150         {
151             return value * 200 / PI;
152         }
153     }else
154     static if (What == Degrees)
155     {
156         static if (In == Radians)
157         {
158             return value * PI / 180;
159         }else
160         static if (In == Gons)
161         {
162             return value * 200 / 180;
163         }
164     }else
165     static if (What == Gons)
166     {
167         static if (In == Radians)
168         {
169             return value * PI / 200;
170         }else
171         static if (In == Degrees)
172         {
173             return value * 180 / 200;
174         }
175     }
176 }
177 
178 alias from = conv; // old name saved.
179 
180 /++
181 Converts degrees to radians.
182 
183 Params:
184     value = The value is in degrees.
185 
186 Returns:
187     The value is in degrees.
188 +/
189 alias degToRad = conv!(Degrees, Radians);
190 
191 /++
192 Convert radians to degrees.
193 
194 Params:
195     value = The value is in radians.
196 
197 Returns:
198     The value is in degrees.
199 +/
200 alias radToDeg = conv!(Radians, Degrees);
201 
202 /++
203 Brings the angle back to normal.
204 
205 Params:
206     Type = Angle type.
207     angle = Angle.
208 
209 Example:
210 ---
211 assert(375.minimize!Degrees == 15);
212     ---
213 +/
214 float minimize(ubyte Type)(float angle) @safe nothrow pure
215 {
216     int k = cast(int) (angle / max!Type);
217     float sign = angle >= 0 ? 1 : -1;
218 
219     return angle - ((max!Type * cast(float) k) * sign);
220 }
221 
222 unittest
223 {
224     assert(minimize!Degrees(361) == 1);
225     assert(minimize!Degrees(470) == (470 - 360));
226     assert(minimize!Degrees(848) == 128);
227 }
228 
229 /++
230 Finds the angle between two angel's. Accepted in any angle change systems.
231 
232 Params:
233     a = First angle.
234     b = Second angle.
235     
236 Returns: Angle between two angel's.
237 +/
238 float betweenAngle(float a, float b) @safe nothrow pure
239 {
240     return (a + b) / 2;
241 }
242 
243 unittest
244 {
245     assert(max!Radians.from!(Radians,Degrees) == (max!Degrees));
246     assert(max!Degrees.from!(Degrees,Gons) == (max!Gons));
247     assert(max!Gons.from!(Gons,Turns) == (max!Turns));
248     
249     assert(rightAngle!Radians.from!(Radians,Degrees) == (rightAngle!Degrees));
250     assert(rightAngle!Degrees.from!(Degrees,Gons) == (rightAngle!Gons));
251 }
252 
253 import tida.vector;
254 
255 /++
256 Returns the angle between two vectors in radians.
257 
258 Params:
259     a = First point.
260     b = Second point.
261     
262 Returns: Angle in radians.
263 
264 To convert, for example, to degrees, use the function like this:
265 ---
266 pointDirection(...).from!(Radians,Degrees);
267     ---
268 +/
269 T pointDirection(T)(Vector!T a, Vector!T b) @safe nothrow pure
270 {
271     import std.math : atan2;
272 
273     T result = atan2(b.y - a.y, b.x - a.x);
274     return result;
275 }
276 
277 /++
278 Rotates the point by the specified number of degrees.
279 
280 Params:
281     vec = Point.
282     angle = Angle of rotation.
283     center = Center of rotation.
284 +/
285 Vecf rotate(Vecf vec, float angle, Vecf center = vecf(0, 0)) @safe nothrow pure
286 {
287     import std.math : sin, cos;
288 
289     float ca = cos(angle);
290     float sa = sin(angle);
291 
292     Vecf result;
293 
294     vec -= center;
295 
296     result.x = vec.x * ca - vec.y * sa;
297     result.y = vec.x * sa + vec.y * ca;
298 
299     return result + center;
300 }
301 
302 /++
303 Convert angle to direction vector. Use when moving an object at a given angle.
304 
305 Params:
306     angle = Angle.
307 
308 Example:
309 ---
310 Vecf vecd = vectorDirection(45.from!(Degrees,Radians));
311 position += vecd * 5; // Move the object 45 degrees at a given speed.
312     ---
313 +/
314 Vector!T vectorDirection(T)(T angle) @safe nothrow pure
315 {
316     import std.math : cos, sin;
317 
318     return vec!T(cos(angle), sin(angle));
319 }