diff --git a/source/nijilive/core/math.d b/source/nijilive/core/math.d new file mode 100644 index 00000000..edbde962 --- /dev/null +++ b/source/nijilive/core/math.d @@ -0,0 +1,361 @@ +/** + nijilive Math Primitives + previously Inochi2D Math Primitives + + Copyright: + Copyright © 2020-2025, Inochi2D Project + + License: + BSD 2-clause License, see LICENSE file. + + Authors: + Luna Nielsen + Asahi Lina +*/ +module nijilive.core.math; +// import nijilive.fmt.serde; +import nijilive.core.meshdata; +import nijilive.core; +public import inmath.linalg; +public import inmath.util; +// public import inmath.dampen; +public import inmath.math; +public import inmath.interpolate; +public import std.math : isFinite; +import std.algorithm; +import std.json; + +/** + An orthographic camera +*/ +class Camera { +private: + mat4 projection; + +public: + + /** + Position of camera + */ + vec2 position = vec2(0, 0); + + /** + Rotation of the camera + */ + float rotation = 0f; + + /** + Size of the camera + */ + vec2 scale = vec2(1, 1); + + /** + Gets the real size of the camera + */ + @property vec2 realSize() { + int width, height; + inGetViewport(width, height); + + return vec2(cast(float)width/scale.x, cast(float)height/scale.y); + } + + deprecated("Use Camera.realSize instead.") + alias getRealSize = realSize; + + /** + Gets the center offset of the camera + */ + @property vec2 centerOffset() { + vec2 realSize = realSize(); + return realSize/2; + } + + deprecated("Use Camera.centerOffset instead.") + alias getCenterOffset = centerOffset; + + /** + Matrix for this camera + */ + @property mat4 matrix() { + if(!position.isFinite) position = vec2(0); + if(!scale.isFinite) scale = vec2(1); + if(!rotation.isFinite) rotation = 0; + + vec2 realSize_ = this.realSize; + if(!realSize_.isFinite) return mat4.identity; + + vec2 origin = vec2(realSize_.x/2, realSize_.y/2); + vec3 pos = vec3(position.x, position.y, -(ushort.max/2)); + + return + mat4.orthographic(0f, realSize.x, realSize.y, 0, 0, ushort.max) * + mat4.translation(origin.x, origin.y, 0) * + mat4.zRotation(rotation) * + mat4.translation(pos); + } +} + +/** + A transform +*/ +struct Transform { +public: + + /** + The translation of the transform + */ + vec3 translation = vec3(0, 0, 0); + + /** + The rotation of the transform + */ + vec3 rotation = vec3(0, 0, 0);//; = quat.identity; + + /** + The scale of the transform + */ + vec2 scale = vec2(1, 1); + + /** + Whether the transform should snap to pixels + */ + bool pixelSnap = false; + + /** + Calculates offset to other vector. + */ + Transform calcOffset(Transform other) { + Transform tnew; + + tnew.translation = this.translation+other.translation; + tnew.rotation = this.rotation+other.rotation; + tnew.scale = this.scale*other.scale; + tnew.update(); + + return tnew; + } + + /** + Returns the result of 2 transforms multiplied together + */ + Transform opBinary(string op : "*")(Transform other) { + Transform tnew; + + mat4 strs = other.trs * this.trs; + + // TRANSLATION + tnew.translation = vec3(strs * vec4(1, 1, 1, 1)); + + // ROTATION + tnew.rotation = this.rotation+other.rotation; + + // SCALE + tnew.scale = this.scale*other.scale; + tnew.trs = strs; + return tnew; + } + + /** + Gets the matrix for this transform + */ + mat4 matrix() { + return trs; + } + + /** + Updates the internal matrix of this transform + */ + void update() { + trs = + mat4.translation(this.translation) * + quat.eulerRotation(this.rotation.x, this.rotation.y, this.rotation.z).toMatrix!(4, 4) * + mat4.scaling(this.scale.x, this.scale.y, 1); + } + + /** + Clears the vector + */ + void clear() { + translation = vec3(0); + rotation = vec3(0); + scale = vec2(1, 1); + } + + /** + Gets a string representation of the transform. + */ + string toString() const { + import std.format : format; + return "%s, %s, %s".format(translation.toString, rotation.toString, scale.toString); + } + + /** + Serializes the transform. + */ + /* TODO + void onSerialize(ref JSONValue object) { + object["trans"] = translation.serialize(); + object["rot"] = rotation.serialize(); + object["scale"] = scale.serialize(); + } + */ + + /** + Deserializes a transform from JSON. + */ + /* TODO + void onDeserialize(ref JSONValue object) { + object.tryGetRef(translation, "trans"); + object.tryGetRef(rotation, "rot"); + object.tryGetRef(scale, "scale"); + } + */ + + // NOTE: This private var is declared here to allow instantiating + // the Transform like prior, but with the added benefit of + // being able to do so with the new auto-generated constructors. +private: + mat4 trs = mat4.identity; +} + + +bool isPointInTriangle(vec2 pt, vec2[3] triangle) { + float sign (ref vec2 p1, ref vec2 p2, ref vec2 p3) { + return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y); + } + vec2 p1 = triangle[0]; + vec2 p2 = triangle[1]; + vec2 p3 = triangle[2]; + + auto d1 = sign(pt, p1, p2); + auto d2 = sign(pt, p2, p3); + auto d3 = sign(pt, p3, p1); + + auto hasNeg = (d1 < 0) || (d2 < 0) || (d3 < 0); + auto hasPos = (d1 > 0) || (d2 > 0) || (d3 > 0); + + return !(hasNeg && hasPos); +} + + +int[] findSurroundingTriangle(vec2 pt, ref MeshData bindingMesh) { + bool isPointInTriangle(vec2 pt, int[] triangle) { + float sign (ref vec2 p1, ref vec2 p2, ref vec2 p3) { + return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y); + } + vec2 p1 = bindingMesh.vertices[triangle[0]]; + vec2 p2 = bindingMesh.vertices[triangle[1]]; + vec2 p3 = bindingMesh.vertices[triangle[2]]; + + auto d1 = sign(pt, p1, p2); + auto d2 = sign(pt, p2, p3); + auto d3 = sign(pt, p3, p1); + + auto hasNeg = (d1 < 0) || (d2 < 0) || (d3 < 0); + auto hasPos = (d1 > 0) || (d2 > 0) || (d3 > 0); + + return !(hasNeg && hasPos); + } + int i = 0; + int[] triangle = [0, 1, 2]; + while (i < bindingMesh.indices.length) { + triangle[0] = bindingMesh.indices[i]; + triangle[1] = bindingMesh.indices[i+1]; + triangle[2] = bindingMesh.indices[i+2]; + if (isPointInTriangle(pt, triangle)) { + return triangle; + } + i += 3; + } + return null; +} + + +// Calculate offset of point in coordinates of triangle. +vec2 calcOffsetInTriangleCoords(vec2 pt, ref MeshData bindingMesh, ref int[] triangle) { + if ((pt - bindingMesh.vertices[triangle[0]]).lengthSquared > + (pt - bindingMesh.vertices[triangle[1]]).lengthSquared) { + swap(triangle[0], triangle[1]); + } + if ((pt - bindingMesh.vertices[triangle[0]]).lengthSquared > + (pt - bindingMesh.vertices[triangle[2]]).lengthSquared) { + swap(triangle[0], triangle[2]); + } + auto p1 = bindingMesh.vertices[triangle[0]]; + auto p2 = bindingMesh.vertices[triangle[1]]; + auto p3 = bindingMesh.vertices[triangle[2]]; + vec2 axis0 = p2 - p1; + float axis0len = axis0.length; + axis0 /= axis0.length; + vec2 axis1 = p3 - p1; + float axis1len = axis1.length; + axis1 /= axis1.length; + + auto relPt = pt - p1; + if (relPt.lengthSquared == 0) + return vec2(0, 0); + float cosA = dot(axis0, axis1); + if (cosA == 0) { + return vec2(dot(relPt, axis0), dot(relPt, axis1)); + } else { + float argA = acos(cosA); + float sinA = sin(argA); + float tanA = tan(argA); + float cosB = dot(axis0, relPt) / relPt.length; + float argB = acos(cosB); + float sinB = sin(argB); + + vec2 ortPt = vec2(relPt.length * cosB, relPt.length * sinB); + + mat2 H = mat2([1, -1/tanA, 0, 1/sinA]); + auto result = H * ortPt; + + return result; + } +} + +// Unsigned short vectors +alias vec2us = Vector!(ushort, 2); /// ditto +alias vec3us = Vector!(ushort, 3); /// ditto +alias vec4us = Vector!(ushort, 4); /// ditto + +/** + Serializes a provided vector type. + + Params: + value = The vector to serialize + dst = The destination JSON value + + Returns: + The serialized vector +*/ +void onSerialize(T)(ref T value, ref JSONValue dst) +if(isVector!T) { + dst = JSONValue.emptyArray; + static foreach(i; 0..T.dimension) { + dst.array ~= JSONValue(isFinite(value.vector[i]) ? value.vector[i] : 0); + } +} + +/** + Gets whether a point is within an axis aligned rectangle +*/ +bool contains(vec4 a, vec2 b) { + return b.x >= a.x && + b.y >= a.y && + b.x <= a.x+a.z && + b.y <= a.y+a.w; +} + +/** + Checks if 2 lines segments are intersecting +*/ +bool areLineSegmentsIntersecting(vec2 p1, vec2 p2, vec2 p3, vec2 p4) { + float epsilon = 0.00001f; + float demoninator = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y); + if (demoninator == 0) return false; + + float uA = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / demoninator; + float uB = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / demoninator; + return (uA > 0+epsilon && uA < 1-epsilon && uB > 0+epsilon && uB < 1-epsilon); +} diff --git a/source/nijilive/math/camera.d b/source/nijilive/math/camera.d deleted file mode 100644 index fba35cca..00000000 --- a/source/nijilive/math/camera.d +++ /dev/null @@ -1,75 +0,0 @@ -/* - nijilive Camera - previously Inochi2D Camera - - Copyright © 2020, Inochi2D Project - Copyright © 2024, nijigenerate Project - Distributed under the 2-Clause BSD License, see LICENSE file. - - Authors: Luna Nielsen -*/ -module nijilive.math.camera; -import nijilive.math; -import nijilive; -import std.math : isFinite; - -/** - An orthographic camera -*/ -class Camera { -private: - mat4 projection; - -public: - - /** - Position of camera - */ - vec2 position = vec2(0, 0); - - /** - Rotation of the camera - */ - float rotation = 0f; - - /** - Size of the camera - */ - vec2 scale = vec2(1, 1); - - vec2 getRealSize() { - int width, height; - inGetViewport(width, height); - - return vec2(cast(float)width/scale.x, cast(float)height/scale.y); - } - - vec2 getCenterOffset() { - vec2 realSize = getRealSize(); - return realSize/2; - } - - /** - Matrix for this camera - - width = width of camera area - height = height of camera area - */ - mat4 matrix() { - if(!position.isFinite) position = vec2(0); - if(!scale.isFinite) scale = vec2(1); - if(!rotation.isFinite) rotation = 0; - - vec2 realSize = getRealSize(); - if(!realSize.isFinite) return mat4.identity; - - vec2 origin = vec2(realSize.x/2, realSize.y/2); - vec3 pos = vec3(position.x, position.y, -(ushort.max/2)); - - return - mat4.orthographic(0f, realSize.x, realSize.y, 0, 0, ushort.max) * - mat4.translation(origin.x, origin.y, 0) * - mat4.zRotation(rotation) * - mat4.translation(pos); - } -} diff --git a/source/nijilive/math/package.d b/source/nijilive/math/package.d deleted file mode 100644 index 9d963128..00000000 --- a/source/nijilive/math/package.d +++ /dev/null @@ -1,61 +0,0 @@ -/* - nijilive Math helpers - previously Inochi2D Math helpers - - Copyright © 2020, Inochi2D Project - Copyright © 2024, nijigenerate Project - Distributed under the 2-Clause BSD License, see LICENSE file. - - Authors: Luna Nielsen -*/ -module nijilive.math; -import inmath.util; -public import inmath.linalg; -public import inmath.math; -public import std.math : isNaN; -public import inmath.interpolate; - -public import nijilive.math.transform; -public import nijilive.math.camera; - -// Unsigned short vectors -alias vec2us = Vector!(ushort, 2); /// ditto -alias vec3us = Vector!(ushort, 3); /// ditto -alias vec4us = Vector!(ushort, 4); /// ditto - -/** - Smoothly dampens from a position to a target -*/ -V dampen(V)(V pos, V target, double delta, double speed = 1) if(isVector!V) { - return (pos - target) * pow(0.001, delta*speed) + target; -} - -/** - Smoothly dampens from a position to a target -*/ -float dampen(float pos, float target, double delta, double speed = 1) { - return (pos - target) * pow(0.001, delta*speed) + target; -} - -/** - Gets whether a point is within an axis aligned rectangle -*/ -bool contains(vec4 a, vec2 b) { - return b.x >= a.x && - b.y >= a.y && - b.x <= a.x+a.z && - b.y <= a.y+a.w; -} - -/** - Checks if 2 lines segments are intersecting -*/ -bool areLineSegmentsIntersecting(vec2 p1, vec2 p2, vec2 p3, vec2 p4) { - float epsilon = 0.00001f; - float demoninator = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y); - if (demoninator == 0) return false; - - float uA = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / demoninator; - float uB = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / demoninator; - return (uA > 0+epsilon && uA < 1-epsilon && uB > 0+epsilon && uB < 1-epsilon); -} diff --git a/source/nijilive/math/serialization.d b/source/nijilive/math/serialization.d deleted file mode 100644 index 6875a50b..00000000 --- a/source/nijilive/math/serialization.d +++ /dev/null @@ -1,45 +0,0 @@ -module nijilive.math.serialization; -import nijilive.fmt.serialize; -import inmath.linalg; -import inmath.util; - -/** - Serializes any size of vector -*/ -void serialize(V, S)(V value, ref S serializer) if(isVector!V) { - auto state = serializer.listBegin(); - static foreach(i; 0..V.dimension) { - serializer.elemBegin; - serializer.serializeValue(value.vector[i]); - } - serializer.listEnd(state); -} - -/** - Serializes any size of matrix -*/ -void serialize(T, S)(T matr, ref S serializer) if(isMatrix!T) { - auto state = serializer.listBegin(); - static foreach(y; 0..T.rows) { - static foreach(x; 0..T.cols) { - serializer.elemBegin; - serializer.serializeValue(matr.matrix[x][y]); - } - } - serializer.listEnd(state); -} - -SerdeException deserialize(V)(ref V value, Fghj data) if (isVector!V) { - int i = 0; - foreach(val; data.byElement) { - - // Some exporters export too many values - if (i >= value.dimension) break; - val.deserializeValue(value.vector[i++]); - } - return null; -} - -bool isEmpty(Fghj value) { - return value == Fghj.init; -} \ No newline at end of file diff --git a/source/nijilive/math/transform.d b/source/nijilive/math/transform.d deleted file mode 100644 index d0557460..00000000 --- a/source/nijilive/math/transform.d +++ /dev/null @@ -1,175 +0,0 @@ -/* - Copyright © 2020, Inochi2D Project - Copyright © 2024, nijigenerate Project - Distributed under the 2-Clause BSD License, see LICENSE file. - - Authors: - Luna Nielsen - Asahi Lina -*/ -module nijilive.math.transform; -public import nijilive.math; -import nijilive.fmt.serialize; - -/** - A transform -*/ -struct Transform { -private: - @Ignore - mat4 trs = mat4.identity; - -public: - - /** - The translation of the transform - */ - vec3 translation = vec3(0, 0, 0); - - /** - The rotation of the transform - */ - vec3 rotation = vec3(0, 0, 0);//; = quat.identity; - - /** - The scale of the transform - */ - vec2 scale = vec2(1, 1); - - /** - Whether the transform should snap to pixels - */ - bool pixelSnap = false; - - /** - Initialize a transform - */ - this(vec3 translation, vec3 rotation = vec3(0), vec2 scale = vec2(1, 1)) { - this.translation = translation; - this.rotation = rotation; - this.scale = scale; - } - - Transform calcOffset(Transform other) { - Transform tnew; - - tnew.translation = this.translation+other.translation; - tnew.rotation = this.rotation+other.rotation; - tnew.scale = this.scale*other.scale; - tnew.update(); - - return tnew; - } - - /** - Returns the result of 2 transforms multiplied together - */ - Transform opBinary(string op : "*")(Transform other) { - Transform tnew; - - mat4 strs = other.trs * this.trs; - - // TRANSLATION - tnew.translation = vec3(strs * vec4(1, 1, 1, 1)); - - // ROTATION - tnew.rotation = this.rotation+other.rotation; - - // SCALE - tnew.scale = this.scale*other.scale; - tnew.trs = strs; - return tnew; - } - - /** - Gets the matrix for this transform - */ - @Ignore - mat4 matrix() { - return trs; - } - - /** - Updates the internal matrix of this transform - */ - void update() { - trs = - mat4.translation(this.translation) * - quat.eulerRotation(this.rotation.x, this.rotation.y, this.rotation.z).toMatrix!(4, 4) * - mat4.scaling(this.scale.x, this.scale.y, 1); - } - - void clear() { - translation = vec3(0); - rotation = vec3(0); - scale = vec2(1, 1); - } - - @Ignore - string toString() { - import std.format : format; - return "%s,\n%s,\n%s\n%s".format(trs.toPrettyString, translation.toString, rotation.toString, scale.toString); - } - - void serialize(S)(ref S serializer) { - auto state = serializer.structBegin(); - serializer.putKey("trans"); - translation.serialize(serializer); - - serializer.putKey("rot"); - rotation.serialize(serializer); - - serializer.putKey("scale"); - scale.serialize(serializer); - - serializer.structEnd(state); - } - - SerdeException deserializeFromFghj(Fghj data) { - translation.deserialize(data["trans"]); - rotation.deserialize(data["rot"]); - scale.deserialize(data["scale"]); - return null; - } -} -/** - A 2D transform; -*/ -struct Transform2D { -private: - @Ignore - mat3 trs; - -public: - /** - Translate - */ - vec2 translation; - /** - Scale - */ - vec2 scale; - - /** - Rotation - */ - float rotation; - - /** - Gets the matrix for this transform - */ - mat3 matrix() { - return trs; - } - - /** - Updates the internal matrix of this transform - */ - void update() { - mat3 translation_ = mat3.translation(vec3(translation, 0)); - mat3 rotation_ = mat3.zRotation(rotation); - mat3 scale_ = mat3.scaling(scale.x, scale.y, 1); - trs = translation_ * rotation_ * scale_; - } - -} diff --git a/source/nijilive/math/triangle.d b/source/nijilive/math/triangle.d deleted file mode 100644 index 83a227e2..00000000 --- a/source/nijilive/math/triangle.d +++ /dev/null @@ -1,164 +0,0 @@ -module nijilive.math.triangle; -import nijilive.math; -import nijilive.core.meshdata; -import nijilive.core.nodes.defstack; -import inmath; -import std.math; -import std.algorithm; -import std.array : array; -import std.conv : to; - - -bool isPointInTriangle(vec2 pt, vec2[3] triangle) { - float sign (ref vec2 p1, ref vec2 p2, ref vec2 p3) { - return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y); - } - vec2 p1 = triangle[0]; - vec2 p2 = triangle[1]; - vec2 p3 = triangle[2]; - - auto d1 = sign(pt, p1, p2); - auto d2 = sign(pt, p2, p3); - auto d3 = sign(pt, p3, p1); - - auto hasNeg = (d1 < 0) || (d2 < 0) || (d3 < 0); - auto hasPos = (d1 > 0) || (d2 > 0) || (d3 > 0); - - return !(hasNeg && hasPos); -} - - -int[] findSurroundingTriangle(vec2 pt, ref MeshData bindingMesh) { - bool isPointInTriangle(vec2 pt, int[] triangle) { - float sign (ref vec2 p1, ref vec2 p2, ref vec2 p3) { - return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y); - } - vec2 p1 = bindingMesh.vertices[triangle[0]]; - vec2 p2 = bindingMesh.vertices[triangle[1]]; - vec2 p3 = bindingMesh.vertices[triangle[2]]; - - auto d1 = sign(pt, p1, p2); - auto d2 = sign(pt, p2, p3); - auto d3 = sign(pt, p3, p1); - - auto hasNeg = (d1 < 0) || (d2 < 0) || (d3 < 0); - auto hasPos = (d1 > 0) || (d2 > 0) || (d3 > 0); - - return !(hasNeg && hasPos); - } - int i = 0; - int[] triangle = [0, 1, 2]; - while (i < bindingMesh.indices.length) { - triangle[0] = bindingMesh.indices[i]; - triangle[1] = bindingMesh.indices[i+1]; - triangle[2] = bindingMesh.indices[i+2]; - if (isPointInTriangle(pt, triangle)) { - return triangle; - } - i += 3; - } - return null; -} - - -// Calculate offset of point in coordinates of triangle. -vec2 calcOffsetInTriangleCoords(vec2 pt, ref MeshData bindingMesh, ref int[] triangle) { - if( (pt - bindingMesh.vertices[triangle[0]]).lengthSquared > (pt - bindingMesh.vertices[triangle[1]]).lengthSquared) { - swap(triangle[0], triangle[1]); - } - if( (pt - bindingMesh.vertices[triangle[0]]).lengthSquared > (pt - bindingMesh.vertices[triangle[2]]).lengthSquared) { - swap(triangle[0], triangle[2]); - } - auto p1 = bindingMesh.vertices[triangle[0]]; - auto p2 = bindingMesh.vertices[triangle[1]]; - auto p3 = bindingMesh.vertices[triangle[2]]; - vec2 axis0 = p2 - p1; - float axis0len = axis0.length; - axis0 /= axis0.length; - vec2 axis1 = p3 - p1; - float axis1len = axis1.length; - axis1 /= axis1.length; - - auto relPt = pt - p1; - if (relPt.lengthSquared == 0) - return vec2(0, 0); - float cosA = dot(axis0, axis1); - if (cosA == 0) { - return vec2(dot(relPt, axis0), dot(relPt, axis1)); - } else { - float argA = acos(cosA); - float sinA = sin(argA); - float tanA = tan(argA); - float cosB = dot(axis0, relPt) / relPt.length; - float argB = acos(cosB); - float sinB = sin(argB); - - vec2 ortPt = vec2(relPt.length * cosB, relPt.length * sinB); - - mat2 H = mat2([1, -1/tanA, 0, 1/sinA]); - auto result = H * ortPt; - - return result; - } -} - -private { -mat3 calculateAffineTransform(vec2[] vertices, int[] triangle, vec2[] deform) { - auto p0 = vertices[triangle[0]]; - auto p1 = vertices[triangle[1]]; - auto p2 = vertices[triangle[2]]; - mat3 original = mat3( - p0.x, p1.x, p2.x, - p0.y, p1.y, p2.y, - 1.0f, 1.0f, 1.0f - ); - auto p3 = p0 + deform[triangle[0]]; - auto p4 = p1 + deform[triangle[1]]; - auto p5 = p2 + deform[triangle[2]]; - - mat3 transformed = mat3( - p3.x, p4.x, p5.x, - p3.y, p4.y, p5.y, - 1.0f, 1.0f, 1.0f); - - mat3 affineTransform = transformed * original.inverse(); - return affineTransform; -} - -vec2 applyAffineTransform(mat3 transform, vec2 point) { - vec3 pointHomogeneous = vec3(point, 1.0); - vec3 transformedPointHomogeneous = transform * pointHomogeneous; - return transformedPointHomogeneous.xy; -} - -float calculateAngle(vec2 A, vec2 B) { - return atan2(B.y - A.y, B.x - A.x); -} -} - -bool nlCalculateTransformInTriangle(vec2[] vertices, int[] triangle, vec2[] deform, vec2 target, - out vec2 target_prime, out float rotationAngle_vert, out float rotationAngle_horz) { - mat3 affineTransform = calculateAffineTransform(vertices, triangle, deform); - target_prime = applyAffineTransform(affineTransform, target); - - // Vertical unit vector rotation - vec2 vert = vec2(0, 1); - vec2 target_vert = target + vert; - vec2 target_vert_prime = applyAffineTransform(affineTransform, target_vert); - - // Horizontal unit vector rotation - vec2 horz = vec2(1, 0); - vec2 target_horz = target + horz; - vec2 target_horz_prime = applyAffineTransform(affineTransform, target_horz); - - // Calculate angles from above vectors. - float originalAngle_vert = calculateAngle(target, target_vert); - float transformedAngle_vert = calculateAngle(target_prime, target_vert_prime); - rotationAngle_vert = transformedAngle_vert - originalAngle_vert; - - float originalAngle_horz = calculateAngle(target, target_horz); - float transformedAngle_horz = calculateAngle(target_prime, target_horz_prime); - rotationAngle_horz = transformedAngle_horz - originalAngle_horz; - - return true; -} \ No newline at end of file