From b1a9867f16755ce7d8bf47b994ac0b3b5810ec34 Mon Sep 17 00:00:00 2001 From: arch1t3cht Date: Fri, 4 Nov 2022 00:45:32 +0100 Subject: [PATCH] Visual perspective tool: First functioning version --- src/command/vis_tool.cpp | 10 ++ src/libresrc/default_toolbar.json | 1 + src/meson.build | 2 + src/vector3d.cpp | 99 +++++++++++ src/vector3d.h | 83 +++++++++ src/visual_tool.cpp | 52 ++++++ src/visual_tool.h | 8 + src/visual_tool_perspective.cpp | 271 ++++++++++++++++++++++++++++++ src/visual_tool_perspective.h | 61 +++++++ 9 files changed, 587 insertions(+) create mode 100644 src/vector3d.cpp create mode 100644 src/vector3d.h create mode 100644 src/visual_tool_perspective.cpp create mode 100644 src/visual_tool_perspective.h diff --git a/src/command/vis_tool.cpp b/src/command/vis_tool.cpp index fcf02f492..cb28d4d2d 100644 --- a/src/command/vis_tool.cpp +++ b/src/command/vis_tool.cpp @@ -23,6 +23,7 @@ #include "../visual_tool_clip.h" #include "../visual_tool_cross.h" #include "../visual_tool_drag.h" +#include "../visual_tool_perspective.h" #include "../visual_tool_rotatexy.h" #include "../visual_tool_rotatez.h" #include "../visual_tool_scale.h" @@ -100,6 +101,14 @@ namespace { STR_HELP("Rotate subtitles on their X and Y axes") }; + struct visual_mode_perspective final : public visual_tool_command { + CMD_NAME("video/tool/perspective") + CMD_ICON(visual_vector_clip_drag) //TODO + STR_MENU("Apply 3D Perspective") + STR_DISP("Apply 3D Perspective") + STR_HELP("Rotate and shear subtitles to make them fit a given quad's perspective") + }; + struct visual_mode_scale final : public visual_tool_command { CMD_NAME("video/tool/scale") CMD_ICON(visual_scale) @@ -191,6 +200,7 @@ namespace cmd { reg(agi::make_unique()); reg(agi::make_unique()); reg(agi::make_unique()); + reg(agi::make_unique()); reg(agi::make_unique()); reg(agi::make_unique()); reg(agi::make_unique()); diff --git a/src/libresrc/default_toolbar.json b/src/libresrc/default_toolbar.json index 07afb90b0..21b95397e 100644 --- a/src/libresrc/default_toolbar.json +++ b/src/libresrc/default_toolbar.json @@ -72,6 +72,7 @@ "video/tool/drag", "video/tool/rotate/z", "video/tool/rotate/xy", + "video/tool/perspective", "video/tool/scale", "video/tool/clip", "video/tool/vector_clip", diff --git a/src/meson.build b/src/meson.build index 72587d366..e57a39544 100644 --- a/src/meson.build +++ b/src/meson.build @@ -141,6 +141,7 @@ aegisub_src = files( 'utils.cpp', 'validators.cpp', 'vector2d.cpp', + 'vector3d.cpp', 'version.cpp', 'video_box.cpp', 'video_controller.cpp', @@ -157,6 +158,7 @@ aegisub_src = files( 'visual_tool_clip.cpp', 'visual_tool_cross.cpp', 'visual_tool_drag.cpp', + 'visual_tool_perspective.cpp', 'visual_tool_rotatexy.cpp', 'visual_tool_rotatez.cpp', 'visual_tool_scale.cpp', diff --git a/src/vector3d.cpp b/src/vector3d.cpp new file mode 100644 index 000000000..6b295b8bc --- /dev/null +++ b/src/vector3d.cpp @@ -0,0 +1,99 @@ +// Copyright (c) 2022, arch1t3cht +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// +// Aegisub Project http://www.aegisub.org/ + +/// @file vector3d.cpp +/// @brief 3D mathematical vector used in visual typesetting +/// @ingroup utility visual_ts +/// + +#include "vector3d.h" + +#include "utils.h" + +#include + +#include +#include + +Vector3D::Vector3D() +: x(std::numeric_limits::min()) +, y(std::numeric_limits::min()) +, z(std::numeric_limits::min()) +{ +} + +Vector3D operator *(float f, Vector3D v) { + return Vector3D(v.X() * f, v.Y() * f, v.Z() * f); +} + +Vector3D operator /(float f, Vector3D v) { + return Vector3D(f / v.X(), f / v.Y(), f / v.Z()); +} + +Vector3D operator +(float f, Vector3D v) { + return Vector3D(v.X() + f, v.Y() + f, v.Z() + f); +} + +Vector3D operator -(float f, Vector3D v) { + return Vector3D(f - v.X(), f - v.Y(), f - v.Z()); +} + +Vector3D Vector3D::Unit() const { + float len = Len(); + if (len == 0) + return Vector3D(0, 0, 0); + return *this / len; +} + +Vector3D Vector3D::RotateX(float angle) const { + return Vector3D(x, y * cos(angle) - z * sin(angle), y * sin(angle) + z * cos(angle)); +} + +Vector3D Vector3D::RotateY(float angle) const { + return Vector3D(x * cos(angle) - z * sin(angle), y, x * sin(angle) + z * cos(angle)); +} + +Vector3D Vector3D::RotateZ(float angle) const { + return Vector3D(x * cos(angle) - y * sin(angle), x * sin(angle) + y * cos(angle), z); +} + +Vector3D Vector3D::Max(Vector3D param) const { + return Vector3D(std::max(x, param.x), std::max(y, param.y), std::max(z, param.z)); +} + +Vector3D Vector3D::Min(Vector3D param) const { + return Vector3D(std::min(x, param.x), std::min(y, param.y), std::max(z, param.z)); +} + +Vector3D Vector3D::Round(float step) const { + return Vector3D(floorf(x / step + .5f) * step, floorf(y / step + .5f) * step, floorf(z / step + .5f)); +} + +Vector3D::operator bool() const { + return *this != Vector3D(); +} + +std::string Vector3D::PStr(char sep) const { + return "(" + Str(sep) + ")"; +} + +std::string Vector3D::DStr(char sep) const { + return agi::format("%d%c%d%c%d", (int)x, sep, (int)y, sep, (int)z); +} + +std::string Vector3D::Str(char sep) const { + return float_to_string(x,2) + sep + float_to_string(y,2) + sep + float_to_string(z, 2); +} diff --git a/src/vector3d.h b/src/vector3d.h new file mode 100644 index 000000000..384d29112 --- /dev/null +++ b/src/vector3d.h @@ -0,0 +1,83 @@ +// Copyright (c) 2022, arch1t3cht +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// +// Aegisub Project http://www.aegisub.org/ + +/// @file vector3d.h +/// @see vector3d.cpp +/// @ingroup utility visual_ts +/// + +#pragma once + +#include +#include +#include "vector2d.h" + +class Vector3D { + float x, y, z; + +public: + float X() const { return x; } + float Y() const { return y; } + float Z() const { return z; } + Vector2D XY() const { return Vector2D(x, y); } + + Vector3D(); + Vector3D(Vector2D xy) : x(xy.X()), y(xy.Y()), z(0.) { } + Vector3D(Vector2D xy, float z) : x(xy.X()), y(xy.Y()), z(z) { } + Vector3D(float x, float y, float z) : x(x), y(y), z(z) { } + + bool operator ==(const Vector3D r) const { return x == r.x && y == r.y; } + bool operator !=(const Vector3D r) const { return x != r.x || y != r.y; } + explicit operator bool() const; + + Vector3D operator -() const { return Vector3D(-x, -y, -z); } + Vector3D operator +(const Vector3D r) const { return Vector3D(x + r.x, y + r.y, z + r.z); } + Vector3D operator -(const Vector3D r) const { return Vector3D(x - r.x, y - r.y, z - r.z); } + Vector3D operator *(const Vector3D r) const { return Vector3D(x * r.x, y * r.y, z * r.z); } + Vector3D operator /(const Vector3D r) const { return Vector3D(x / r.x, y / r.y, z / r.z); } + Vector3D operator +(float param) const { return Vector3D(x + param, y + param, z + param); } + Vector3D operator -(float param) const { return Vector3D(x - param, y - param, z - param); } + Vector3D operator *(float param) const { return Vector3D(x * param, y * param, z * param); } + Vector3D operator /(float param) const { return Vector3D(x / param, y / param, z / param); } + + Vector3D Unit() const; + + Vector3D RotateX(float angle) const; + Vector3D RotateY(float angle) const; + Vector3D RotateZ(float angle) const; + + Vector3D Max(Vector3D param) const; + Vector3D Min(Vector3D param) const; + Vector3D Round(float step) const; + + Vector3D Cross(const Vector3D param) const { return Vector3D(y * param.z - z * param.y, z * param.x - x * param.z, x * param.y - y * param.x); } + float Dot(const Vector3D param) const { return x * param.x + y * param.y + z * param.z; } + + float Len() const { return sqrt(x*x + y*y + z*z); } + float SquareLen() const { return x*x + y*y + z*z; } + + /// Get as string with given separator + std::string Str(char sep = ',') const; + /// Get as string surrounded by parentheses with given separator + std::string PStr(char sep = ',') const; + /// Get as string with given separator with values rounded to ints + std::string DStr(char sep = ',') const; +}; + +Vector3D operator * (float f, Vector3D v); +Vector3D operator / (float f, Vector3D v); +Vector3D operator + (float f, Vector3D v); +Vector3D operator - (float f, Vector3D v); diff --git a/src/visual_tool.cpp b/src/visual_tool.cpp index 61d320722..398ff1954 100644 --- a/src/visual_tool.cpp +++ b/src/visual_tool.cpp @@ -23,6 +23,7 @@ #include "ass_dialogue.h" #include "ass_file.h" #include "ass_style.h" +#include "auto4_base.h" #include "compat.h" #include "include/aegisub/context.h" #include "options.h" @@ -471,6 +472,57 @@ void VisualToolBase::GetLineScale(AssDialogue *diag, Vector2D &scale) { scale = Vector2D(x, y); } +float VisualToolBase::GetLineFontSize(AssDialogue *diag) { + float fs = 0.f; + + if (AssStyle *style = c->ass->GetStyle(diag->Style)) + fs = style->fontsize; + auto blocks = diag->ParseTags(); + if (param_vec tag = find_tag(blocks, "\\fs")) + fs = tag->front().Get(fs); + + return fs; +} + +int VisualToolBase::GetLineAlignment(AssDialogue *diag) { + int an = 0; + + if (AssStyle *style = c->ass->GetStyle(diag->Style)) + an = style->alignment; + auto blocks = diag->ParseTags(); + if (param_vec tag = find_tag(blocks, "\\an")) + an = tag->front().Get(an); + + return an; +} + +void VisualToolBase::GetLineBaseExtents(AssDialogue *diag, double &width, double &height, double &descent, double &extlead) { + width = 0.; + height = 0.; + descent = 0.; + extlead = 0.; + + AssStyle style; + if (AssStyle *basestyle = c->ass->GetStyle(diag->Style)) { + style = AssStyle(basestyle->GetEntryData()); + style.scalex = 100.; + style.scaley = 100.; + } + + auto blocks = diag->ParseTags(); + if (param_vec tag = find_tag(blocks, "\\fs")) + style.fontsize = tag->front().Get(style.fontsize); + if (param_vec tag = find_tag(blocks, "\\fn")) + style.font = tag->front().Get(style.font); + + std::string text = diag->GetStrippedText(); + if (!Automation4::CalculateTextExtents(&style, text, width, height, descent, extlead)) { + // meh... let's make some ballpark estimates + width = style.fontsize * text.length(); + height = style.fontsize; + } +} + void VisualToolBase::GetLineClip(AssDialogue *diag, Vector2D &p1, Vector2D &p2, bool &inverse) { inverse = false; diff --git a/src/visual_tool.h b/src/visual_tool.h index 72eec4224..bfbe3a8b1 100644 --- a/src/visual_tool.h +++ b/src/visual_tool.h @@ -127,6 +127,14 @@ protected: void GetLineRotation(AssDialogue *diag, float &rx, float &ry, float &rz); void GetLineShear(AssDialogue *diag, float& fax, float& fay); void GetLineScale(AssDialogue *diag, Vector2D &scale); + float GetLineFontSize(AssDialogue *diag); + int GetLineAlignment(AssDialogue *diag); + /// @brief Compute text extents of the given line without any formatting + /// + /// Formatting tags are stripped and \fs tags are respected, but \fscx and \fscy are kept as 100 even if + /// they are different in the style. + /// Returns a rough estimate when getting the precise extents fails + void GetLineBaseExtents(AssDialogue *diag, double &width, double &height, double &descent, double &extlead); void GetLineClip(AssDialogue *diag, Vector2D &p1, Vector2D &p2, bool &inverse); std::string GetLineVectorClip(AssDialogue *diag, int &scale, bool &inverse); diff --git a/src/visual_tool_perspective.cpp b/src/visual_tool_perspective.cpp new file mode 100644 index 000000000..8c73ae85f --- /dev/null +++ b/src/visual_tool_perspective.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2022, arch1t3cht +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// +// Aegisub Project http://www.aegisub.org/ + +/// @file visual_tool_perspective.cpp +/// @brief 3D perspective visual typesetting tool +/// @ingroup visual_ts + +#include "visual_tool_perspective.h" + +#include "compat.h" +#include "include/aegisub/context.h" +#include "options.h" +#include "selection_controller.h" +#include "vector3d.h" + +#include + +#include +#include + +static const float deg2rad = 3.1415926536f / 180.f; +static const float rad2deg = 180.f / 3.1415926536f; +static const float screen_z = 312.5; + +void VisualToolPerspective::Solve2x2Proper(float a11, float a12, float a21, float a22, float b1, float b2, float &x1, float &x2) { + // LU decomposition + // i = 1 + a21 = a21 / a11; + // i = 2 + a22 = a22 - a21 * a12; + // forward substitution + float z1 = b1; + float z2 = b2 - a21 * z1; + // backward substitution + x2 = z2 / a22; + x1 = (z1 - a12 * x2) / a11; +} + + +void VisualToolPerspective::Solve2x2(float a11, float a12, float a21, float a22, float b1, float b2, float &x1, float &x2) { + // Simple pivoting + if (abs(a11) >= abs(a21)) + Solve2x2Proper(a11, a12, a21, a22, b1, b2, x1, x2); + else + Solve2x2Proper(a21, a22, a11, a12, b2, b1, x1, x2); +} + + +VisualToolPerspective::VisualToolPerspective(VideoDisplay *parent, agi::Context *context) +: VisualTool(parent, context) +{ + orgf = new Feature; + orgf->type = DRAG_BIG_TRIANGLE; + features.push_back(*orgf); + + for (int i = 0; i < 4; i++) { + quad_corners.push_back(new Feature); + old_positions.push_back(Vector2D()); + quad_corners.back()->type = DRAG_SMALL_CIRCLE; + features.push_back(*quad_corners.back()); + } +} + +void VisualToolPerspective::Draw() { + if (!active_line) return; + + wxColour line_color = to_wx(line_color_primary_opt->GetColor()); + gl.SetLineColour(line_color); + + for (int i = 0; i < 4; i++) { + gl.DrawDashedLine(quad_corners[i]->pos, quad_corners[(i + 1) % 4]->pos, 6); + } + + DrawAllFeatures(); +} + +void VisualToolPerspective::UpdateDrag(Feature *feature) { + if (feature == orgf) { + Vector2D diff = orgf->pos - old_orgf; + for (int i = 0; i < 4; i++) { + quad_corners[i]->pos = quad_corners[i]->pos + diff; + } + } + Vector2D q1 = ToScriptCoords(quad_corners[0]->pos); + Vector2D q2 = ToScriptCoords(quad_corners[1]->pos); + Vector2D q3 = ToScriptCoords(quad_corners[2]->pos); + Vector2D q4 = ToScriptCoords(quad_corners[3]->pos); + + Vector2D diag1 = q3 - q1; + Vector2D diag2 = q2 - q4; + Vector2D b = q4 - q1; + float center_la1, center_la2; + Solve2x2(diag1.X(), diag2.X(), diag1.Y(), diag2.Y(), b.X(), b.Y(), center_la1, center_la2); + if (center_la1 < 0 || center_la1 > 1 || -center_la2 < 0 || -center_la2 > 1) { + ResetFeaturePositions(); + return; + } + Vector2D center = q1 + center_la1 * diag1; + + // Normalize to center + q1 = q1 - center; + q2 = q2 - center; + q3 = q3 - center; + q4 = q4 - center; + + // Find a parallelogram projecting to the quad + float z2, z4; + Vector2D side1 = q2 - q3; + Vector2D side2 = q4 - q3; + Solve2x2(side1.X(), side2.X(), side1.Y(), side2.Y(), -diag1.X(), -diag1.Y(), z2, z4); + + float scalefactor = (z2 + z4) / 2; + Vector3D r1 = Vector3D(q1, screen_z) / scalefactor; + Vector3D r2 = z2 * Vector3D(q2, screen_z) / scalefactor; + Vector3D r4 = z4 * Vector3D(q4, screen_z) / scalefactor; + + // Find the rotations + Vector3D n = (r2 - r1).Cross(r4 - r1); + float roty = atan(n.X() / n.Z()); + n = n.RotateY(roty); + float rotx = atan(n.Y() / n.Z()); + + Vector3D ab = (r2 - r1).RotateY(roty).RotateX(rotx); + float rotz = atan(ab.Y() / ab.X()); + + Vector3D ad = (r4 - r1).RotateY(roty).RotateX(rotx).RotateZ(-rotz); + float fax = ad.X() / ad.Y(); + + float scalex = ab.Len() / textwidth; + float scaley = ad.Y() / textheight; + + for (auto line : c->selectionController->GetSelectedSet()) { + angle_x = rotx * rad2deg; + angle_y = -roty * rad2deg; + angle_z = -rotz * rad2deg; + orgf->pos = FromScriptCoords(center); + SetOverride(line, "\\fax", agi::format("%.6f", fax * scaley / scalex)); + SetOverride(line, "\\fay", agi::format("%.4f", 0)); // TODO just kill the tag + SetOverride(line, "\\fscx", agi::format("%.2f", 100 * scalex)); + SetOverride(line, "\\fscy", agi::format("%.2f", 100 * scaley)); + SetOverride(line, "\\frz", agi::format("%.4f", angle_z)); + SetOverride(line, "\\frx", agi::format("%.4f", angle_x)); + SetOverride(line, "\\fry", agi::format("%.4f", angle_y)); + SetOverride(line, "\\pos", (center - Vector2D(fax_shift_factor * fax * scaley, 0)).PStr()); + SetOverride(line, "\\org", center.PStr()); + } + SaveFeaturePositions(); +} + +void VisualToolPerspective::ResetFeaturePositions() { + for (int i = 0; i < 4; i++) { + quad_corners[i]->pos = old_positions[i]; + } + orgf->pos = old_orgf; +} + +void VisualToolPerspective::SaveFeaturePositions() { + for (int i = 0; i < 4; i++) { + old_positions[i] = quad_corners[i]->pos; + } + old_orgf = orgf->pos; +} + +void VisualToolPerspective::SetFeaturePositions() { + double textleft, texttop = 0.; + + switch ((align - 1) % 3) { + case 1: + textleft = -textwidth / 2; + break; + case 2: + textleft = -textwidth; + break; + default: + break; + } + switch ((align - 1) / 3) { + case 0: + texttop = -textheight; + break; + case 1: + texttop = -textheight / 2; + break; + default: + break; + } + + Vector2D textrect[] = { + Vector2D(0, 0), + Vector2D(textwidth, 0), + Vector2D(textwidth, textheight), + Vector2D(0, textheight), + }; + + for (int i = 0; i < 4; i++) { + Vector2D p = textrect[i]; + // Apply \fax and \fay + p = Vector2D(p.X() + p.Y() * fax, p.X() * fay + p.Y()); + // Translate to alignment point + p = p + Vector2D(textleft, texttop); + // Apply scaling + p = Vector2D(p.X() * fsc.X() / 100., p.Y() * fsc.Y() / 100.); + // Translate relative to origin + p = p + pos - org; + // Rotate ZXY + Vector3D q(p); + q = q.RotateZ(-angle_z * deg2rad); + q = q.RotateX(-angle_x * deg2rad); + q = q.RotateY(angle_y * deg2rad); + // Project + q = (screen_z / (q.Z() + screen_z)) * q; + // Move to origin + Vector2D r = q.XY() + org; + + quad_corners[i]->pos = FromScriptCoords(r); + } + + if (!(orgf->pos = org)) + orgf->pos = pos; + orgf->pos = FromScriptCoords(orgf->pos); + SaveFeaturePositions(); +} + +void VisualToolPerspective::DoRefresh() { + if (!active_line) return; + + org = GetLineOrigin(active_line); + pos = GetLinePosition(active_line); + if (!org) + org = pos; + + GetLineRotation(active_line, angle_x, angle_y, angle_z); + GetLineShear(active_line, fax, fay); + GetLineScale(active_line, fsc); + + float fs = GetLineFontSize(active_line); + align = GetLineAlignment(active_line); + + switch (align) { + case 1: + case 2: + case 3: + fax_shift_factor = fs; + break; + case 4: + case 5: + case 6: + fax_shift_factor = fs / 2; + break; + default: + fax_shift_factor = 0.; + break; + } + + double descend, extlead; + GetLineBaseExtents(active_line, textwidth, textheight, descend, extlead); + SetFeaturePositions(); +} diff --git a/src/visual_tool_perspective.h b/src/visual_tool_perspective.h new file mode 100644 index 000000000..1467fb635 --- /dev/null +++ b/src/visual_tool_perspective.h @@ -0,0 +1,61 @@ +// Copyright (c) 2022, arch1t3cht +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// +// Aegisub Project http://www.aegisub.org/ + +/// @file visual_tool_perspective.h +/// @see visual_tool_perspective.cpp +/// @ingroup visual_ts +/// + +#include "visual_feature.h" +#include "visual_tool.h" + +class VisualToolPerspective final : public VisualTool { + float angle_x = 0.f; /// Current x rotation + float angle_y = 0.f; /// Current y rotation + float angle_z = 0.f; /// Current z rotation + + float fax = 0.f; + float fay = 0.f; + + int align = 0; + float fax_shift_factor = 0.f; + + double textwidth = 0.f; + double textheight = 0.f; + + Vector2D fsc; + + Vector2D org; + Vector2D pos; + + Feature *orgf; + Vector2D old_orgf; + + std::vector old_positions; + std::vector quad_corners; + + void Solve2x2Proper(float a11, float a12, float a21, float a22, float b1, float b2, float &x1, float &x2); + void Solve2x2(float a11, float a12, float a21, float a22, float b1, float b2, float &x1, float &x2); + + void DoRefresh() override; + void Draw() override; + void UpdateDrag(Feature *feature) override; + void SetFeaturePositions(); + void ResetFeaturePositions(); + void SaveFeaturePositions(); +public: + VisualToolPerspective(VideoDisplay *parent, agi::Context *context); +};