1 /**
2     Inochi2D Math Primitives
3 
4     Copyright: 
5         Copyright © 2020-2026, Inochi2D Project
6     
7     License:
8         $(LINK2 https://github.com/Inochi2D/inochi2d/blob/main/LICENSE, BSD 2-clause License)
9     
10     Authors:
11         Luna Nielsen
12         Hoshino Lina
13 */
14 module inochi2d.core.math;
15 import inochi2d.core;
16 import nulib.math;
17 import numem;
18 
19 public import inochi2d.core.math.transform;
20 public import inochi2d.core.math.deform;
21 public import inochi2d.core.math.trig;
22 public import numath.dampen;
23 public import numath;
24 
25 /**
26     A camera
27 */
28 abstract
29 class Camera : NuRefCounted {
30 
31     /**
32         Size of the camera's viewport.
33     */
34     vec2 size = vec2(1, 1);
35 
36     /**
37         The view-projection matrix for the camera.
38     */
39     abstract @property mat4 matrix() @nogc;
40 
41     /**
42         Updates the state of the camera.
43     */
44     abstract void update() @nogc;
45 }
46 
47 /**
48     An orthographic camera
49 */
50 class Camera2D : Camera {
51 private:
52 @nogc:
53     mat4 projection;
54 
55 public:
56 
57     /**
58         Position of camera
59     */
60     vec2 position = vec2(0, 0);
61 
62     /**
63         Rotation of the camera
64     */
65     float rotation = 0f;
66 
67     /**
68         Scale to apply to the camera's viewport.
69     */
70     float scale = 1;
71 
72     /**
73         Gets the center offset of the camera
74     */
75     @property vec2 centerOffset() => size / 2.0;
76 
77     /**
78         Matrix for this camera
79     */
80     override
81     @property mat4 matrix() @nogc => projection;
82 
83     /**
84         Updates the state of the camera.
85     */
86     override
87     void update() @nogc {
88         if (!position.isFinite)
89             position = vec2(0);
90         if (!scale.isFinite)
91             scale = 1;
92         if (!rotation.isFinite)
93             rotation = 0;
94 
95         vec2 origin = vec2(size.x / 2, size.y / 2);
96         vec3 pos = vec3(position.x, position.y, -(ushort.max / 2));
97         projection =
98             mat4.orthographic(0f, size.x, size.y, 0, 0.001, ushort.max) *
99             mat4.translation(origin.x, origin.y, 0) *
100             mat4.scaling(scale, scale, 1) *
101             mat4.zRotation(rotation) *
102             mat4.translation(pos);
103     }
104 }
105 
106 /**
107     Gets a relative vector between 2 matrices.
108 
109     Params:
110         lhs = The left hand side matrix.
111         rhs = The right hand side matrix.
112     
113     Returns:
114         A vector describing the relative translation between
115         the 2 matrices.
116 */
117 vec3 relativeVectorTo(mat4 lhs, mat4 rhs) @nogc pure {
118     mat4 cm = (lhs.inverse * rhs).translation;
119     return vec3(cm.matrix[0][3], cm.matrix[1][3], cm.matrix[2][3]);
120 }
121 
122 /**
123     Gets a relative vector between 2 matrices, with the 
124     multiplication-order of the left-hand and right-hand 
125     side inverted.
126 
127     Params:
128         lhs = The left hand side matrix.
129         rhs = The right hand side matrix.
130     
131     Returns:
132         A vector describing the relative translation between
133         the 2 matrices.
134 */
135 vec3 relativeVectorToInverse(mat4 lhs, mat4 rhs) @nogc pure {
136     mat4 cm = (rhs * lhs.inverse).translation;
137     return vec3(cm.matrix[0][3], cm.matrix[1][3], cm.matrix[2][3]);
138 }
139 
140 int[] findSurroundingTriangle(vec2 pt, ref MeshData bindingMesh) {
141     bool isPointInTriangle(vec2 pt, int[] triangle) {
142         float sign(ref vec2 p1, ref vec2 p2, ref vec2 p3) {
143             return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
144         }
145 
146         vec2 p1 = bindingMesh.vertices[triangle[0]];
147         vec2 p2 = bindingMesh.vertices[triangle[1]];
148         vec2 p3 = bindingMesh.vertices[triangle[2]];
149 
150         auto d1 = sign(pt, p1, p2);
151         auto d2 = sign(pt, p2, p3);
152         auto d3 = sign(pt, p3, p1);
153 
154         auto hasNeg = (d1 < 0) || (d2 < 0) || (d3 < 0);
155         auto hasPos = (d1 > 0) || (d2 > 0) || (d3 > 0);
156 
157         return !(hasNeg && hasPos);
158     }
159 
160     int i = 0;
161     int[] triangle = [0, 1, 2];
162     while (i < bindingMesh.indices.length) {
163         triangle[0] = bindingMesh.indices[i];
164         triangle[1] = bindingMesh.indices[i + 1];
165         triangle[2] = bindingMesh.indices[i + 2];
166         if (isPointInTriangle(pt, triangle)) {
167             return triangle;
168         }
169         i += 3;
170     }
171     return null;
172 }
173 
174 // Calculate offset of point in coordinates of triangle.
175 vec2 calcOffsetInTriangleCoords(vec2 pt, ref MeshData bindingMesh, ref int[] triangle) {
176     if ((pt - bindingMesh.vertices[triangle[0]]).lengthSquared >
177             (pt - bindingMesh.vertices[triangle[1]]).lengthSquared) {
178         nu_swap(triangle[0], triangle[1]);
179     }
180     if ((pt - bindingMesh.vertices[triangle[0]]).lengthSquared >
181             (pt - bindingMesh.vertices[triangle[2]]).lengthSquared) {
182         nu_swap(triangle[0], triangle[2]);
183     }
184     auto p1 = bindingMesh.vertices[triangle[0]];
185     auto p2 = bindingMesh.vertices[triangle[1]];
186     auto p3 = bindingMesh.vertices[triangle[2]];
187     vec2 axis0 = p2 - p1;
188     float axis0len = axis0.length;
189     axis0 /= axis0.length;
190     vec2 axis1 = p3 - p1;
191     float axis1len = axis1.length;
192     axis1 /= axis1.length;
193 
194     auto relPt = pt - p1;
195     if (relPt.lengthSquared == 0)
196         return vec2(0, 0);
197     float cosA = dot(axis0, axis1);
198     if (cosA == 0) {
199         return vec2(dot(relPt, axis0), dot(relPt, axis1));
200     } else {
201         float argA = acos(cosA);
202         float sinA = sin(argA);
203         float tanA = tan(argA);
204         float cosB = dot(axis0, relPt) / relPt.length;
205         float argB = acos(cosB);
206         float sinB = sin(argB);
207 
208         vec2 ortPt = vec2(relPt.length * cosB, relPt.length * sinB);
209 
210         mat2 H = mat2([1, -1 / tanA, 0, 1 / sinA]);
211         auto result = H * ortPt;
212 
213         return result;
214     }
215 }
216 
217 // Unsigned short vectors
218 alias vec2us = VectorImpl!(ushort, 2); /// ditto
219 alias vec3us = VectorImpl!(ushort, 3); /// ditto
220 alias vec4us = VectorImpl!(ushort, 4); /// ditto
221 
222 /**
223     Serializes a provided vector type.
224 
225     Params:
226         value = The vector to serialize
227         dst =   The destination JSON value
228     
229     Returns:
230         The serialized vector
231 */
232 void onSerialize(T)(ref T value, ref DataNode dst) @nogc
233 if (isVector!T) {
234     dst = DataNode.createArray();
235     static foreach (i; 0 .. T.dimensions) {
236         static if (__traits(isFloating, T)) {
237             dst.array ~= DataNode(isFinite(value.data[i]) ? value.data[i] : 0);
238         } else {
239             dst.array ~= DataNode(value.data[i]);
240         }
241     }
242 }
243 
244 /**
245     Gets whether a point is within an axis aligned rectangle
246 */
247 bool contains(vec4 a, vec2 b) {
248     return b.x >= a.x &&
249         b.y >= a.y &&
250         b.x <= a.x + a.z &&
251         b.y <= a.y + a.w;
252 }
253 
254 /**
255     Checks if 2 lines segments are intersecting
256 */
257 bool areLineSegmentsIntersecting(vec2 p1, vec2 p2, vec2 p3, vec2 p4) {
258     float epsilon = 0.00001f;
259     float demoninator = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);
260     if (demoninator == 0)
261         return false;
262 
263     float uA = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / demoninator;
264     float uB = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / demoninator;
265     return (uA > 0 + epsilon && uA < 1 - epsilon && uB > 0 + epsilon && uB < 1 - epsilon);
266 }
267 
268 /**
269     Different modes of interpolation between values.
270 */
271 enum InterpolateMode : uint {
272 
273     /**
274         Round to nearest
275     */
276     nearest = 0,
277 
278     /**
279         Linear interpolation
280     */
281     linear = 1,
282 
283     /**
284         Round to nearest
285     */
286     stepped = 2,
287 
288     /**
289         Interpolation using quadratic interpolation
290     */
291     quadratic = 3,
292 
293     /**
294         Cubic interpolation
295     */
296     cubic = 4,
297 }
298 
299 /**
300     Converts a string key into a interpolation mode.
301 */
302 InterpolateMode toInterpolateMode(string key) @nogc {
303     switch (key) {
304 
305     case "nearest":
306     case "Nearest":
307         return InterpolateMode.nearest;
308 
309     case "linear":
310     case "Linear":
311         return InterpolateMode.linear;
312 
313     case "stepped":
314     case "Stepped":
315         return InterpolateMode.stepped;
316 
317     case "bezier":
318     case "Bezier":
319     case "quadratic":
320         return InterpolateMode.quadratic;
321 
322     case "cubic":
323     case "Cubic":
324         return InterpolateMode.cubic;
325 
326     default:
327         return InterpolateMode.linear;
328     }
329 }