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 }