1 module sbylib.math.angle;
2 
3 import std.format : format;
4 
5 /**
6 This struct is like (Degree | Radian)
7 */
8 struct Angle {
9     private float deg;
10 
11     package this(float deg) {
12         this.deg = deg;
13     }
14 
15     /**
16     unary operator for '-' 
17 
18     Returns: negated angle struct
19     */
20     Angle opUnary(string op)() const
21     if (op == "-")
22     {
23         return Angle(mixin(op ~ "this.deg"));
24     }
25 
26     /**
27     binary operator between float ("*" and "/" only)
28 
29     Params:
30         v = operation target value
31 
32     Returns: calculated angle struct
33     */
34     Angle opBinary(string op)(float v) const
35     if (op == "*" || op == "/") 
36     {
37         return Angle(mixin("this.deg " ~ op ~ " v"));
38     }
39 
40     /**
41     binary operator between float ("*" and "/" only)
42 
43     Params:
44         v = operation target value
45 
46     Returns: calculated angle struct
47     */
48     Angle opBinaryRight(string op)(float v) const
49     if (op == "*" || op == "/") 
50     {
51         return Angle(mixin("v" ~ op ~ "this.deg"));
52     }
53 
54     /**
55     binary operator between Angle ("*" and "/" only)
56 
57     Params:
58         a = operation target
59 
60     Returns: calculated angle struct
61     */
62     Angle opBinary(string op)(Angle a) const {
63         return Angle(mixin("this.deg " ~ op ~ " a.deg"));
64     }
65 
66     /**
67     operator assign between Angle ("*" and "/" only)
68 
69     Params:
70         a = operation target
71     */
72     void opOpAssign(string op)(float v)
73     if (op == "*" || op == "/") 
74     {
75         mixin("this.deg" ~ op ~ "= v;");
76     }
77 
78     /**
79     operator assign between Angle ("*" and "/" only)
80 
81     Params:
82         a = operation target
83     */
84     void opOpAssign(string op)(Angle a) {
85         mixin("this.deg" ~ op ~ "= a.deg;");
86     }
87 
88     /**
89     compare operation between Angle
90 
91     Params:
92         a = comparison target
93 
94     Returns: comparison result
95     */
96     int opCmp(Angle a) const {
97         import std.math : sgn;
98 
99         return cast(int)sgn(this.deg - a.deg);
100     }
101 
102     /**
103     equal operation between Angle
104 
105     Params:
106         a = comparison target
107 
108     Returns: comparison result
109     */
110     bool opEquals(Angle a) const {
111         return this.deg == a.deg;
112     }
113 
114     /**
115     Calculate the hash value for this instance.
116     It is equals to the floating degree expression's hash value.
117 
118     Returns: hash value
119     */
120     int toHash() const {
121         return this.deg.deg.toHash();
122     }
123 
124     /**
125     convers to String
126 
127     Returns: String expression
128     */
129     string toString() const {
130         return format!"%f [deg.]"(deg);
131     }
132 
133     /**
134     Get the floating value as radians.
135 
136     Returns: radian value
137     */
138     float asRadian() const {
139         import std.math : PI;
140         return deg * PI / 180;
141     }
142 
143     /**
144     Get the floating value as degrees.
145 
146     Returns: degree value
147     */
148     float asDegree() const {
149         return deg;
150     }
151 }
152 
153 private mixin template RadianInputFunction(string func) {
154     import std : replace;
155     mixin(q{
156 auto ${func}(const Angle angle) {
157     import std.math : ${func};
158     return ${func}(angle.asRadian());
159 }
160     }.replace("${func}", func));
161 }
162 
163 private mixin template RadianOutputFunction(string func) {
164     import std : replace;
165     mixin(format!q{
166 auto ${func}(float angle) {
167     import std.math : ${func};
168     return ${func}(angle).rad;
169 }
170     }.replace("${func}", func));
171 }
172 
173 private mixin template AngleInputFunction(string func) {
174     import std : replace;
175     mixin(format!q{
176 auto ${func}(const Angle angle) {
177     import std.math : ${func};
178     return ${func}(angle.asDegree()).deg;
179 }
180 
181     }.replace("${func}", func));
182 }
183 
184 mixin RadianInputFunction!("sin");
185 mixin RadianInputFunction!("cos");
186 mixin RadianInputFunction!("tan");
187 mixin RadianOutputFunction!("asin");
188 mixin RadianOutputFunction!("acos");
189 mixin RadianOutputFunction!("atan");
190 mixin AngleInputFunction!("abs");
191 
192 /**
193 Get Angle struct from degree value.
194 
195 Returns: Angle struct that equals to the degree value.
196 */
197 Angle deg(float d) {
198     return Angle(d);
199 }
200 
201 /**
202 Get Angle struct from radian value.
203 
204 Returns: Angle struct that equals to the radian value.
205 */
206 Angle rad(float r) {
207     import std.math : PI;
208     return Angle(r * 180 / PI);
209 }