diff --git a/app/node/distort/CMakeLists.txt b/app/node/distort/CMakeLists.txt
index 2962e21ca..375232ccc 100644
--- a/app/node/distort/CMakeLists.txt
+++ b/app/node/distort/CMakeLists.txt
@@ -14,6 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+add_subdirectory(cornerpin)
add_subdirectory(crop)
add_subdirectory(flip)
add_subdirectory(transform)
diff --git a/app/node/distort/cornerpin/CMakeLists.txt b/app/node/distort/cornerpin/CMakeLists.txt
new file mode 100644
index 000000000..e89b0f94f
--- /dev/null
+++ b/app/node/distort/cornerpin/CMakeLists.txt
@@ -0,0 +1,22 @@
+# Olive - Non-Linear Video Editor
+# Copyright (C) 2021 Olive Team
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+set(OLIVE_SOURCES
+ ${OLIVE_SOURCES}
+ node/distort/cornerpin/cornerpindistortnode.cpp
+ node/distort/cornerpin/cornerpindistortnode.h
+ PARENT_SCOPE
+)
diff --git a/app/node/distort/cornerpin/cornerpindistortnode.cpp b/app/node/distort/cornerpin/cornerpindistortnode.cpp
new file mode 100644
index 000000000..b1e9b573e
--- /dev/null
+++ b/app/node/distort/cornerpin/cornerpindistortnode.cpp
@@ -0,0 +1,216 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2021 Olive Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+***/
+
+#include "cornerpindistortnode.h"
+
+#include "common/lerp.h"
+#include "core.h"
+#include "widget/slider/floatslider.h"
+
+namespace olive {
+
+const QString CornerPinDistortNode::kTextureInput = QStringLiteral("tex_in");
+const QString CornerPinDistortNode::kTopLeftInput = QStringLiteral("top_left_in");
+const QString CornerPinDistortNode::kTopRightInput = QStringLiteral("top_right_in");
+const QString CornerPinDistortNode::kBottomRightInput = QStringLiteral("bottom_right_in");
+const QString CornerPinDistortNode::kBottomLeftInput = QStringLiteral("bottom_left_in");
+const QString CornerPinDistortNode::kPerspectiveInput = QStringLiteral("perspective_in");
+
+CornerPinDistortNode::CornerPinDistortNode()
+{
+ AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
+ AddInput(kPerspectiveInput, NodeValue::kBoolean, true);
+ AddInput(kTopLeftInput, NodeValue::kVec2, QVector2D(0.0, 0.0));
+ AddInput(kTopRightInput, NodeValue::kVec2, QVector2D(0.0, 0.0));
+ AddInput(kBottomRightInput, NodeValue::kVec2, QVector2D(0.0, 0.0));
+ AddInput(kBottomLeftInput, NodeValue::kVec2, QVector2D(0.0, 0.0));
+}
+
+void CornerPinDistortNode::Retranslate()
+{
+ SetInputName(kTextureInput, tr("Texture"));
+ SetInputName(kPerspectiveInput, tr("Perspective"));
+ SetInputName(kTopLeftInput, tr("Top Left"));
+ SetInputName(kTopRightInput, tr("Top Right"));
+ SetInputName(kBottomRightInput, tr("Bottom Right"));
+ SetInputName(kBottomLeftInput, tr("Bottom Left"));
+}
+
+void CornerPinDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
+{
+ ShaderJob job;
+ job.InsertValue(value);
+ job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
+ job.InsertValue(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this));
+
+ // Convert slider values to their pixel values and then convert to clip space (-1.0 ... 1.0) for overriding the
+ // vertex coordinates.
+ const QVector2D &resolution = globals.resolution();
+ QVector2D half_resolution = resolution * 0.5;
+ QVector2D top_left = QVector2D(ValueToPixel(0, value, resolution)) / half_resolution - QVector2D(1.0, 1.0);
+ QVector2D top_right = QVector2D(ValueToPixel(1, value, resolution)) / half_resolution - QVector2D(1.0, 1.0);
+ QVector2D bottom_right = QVector2D(ValueToPixel(2, value, resolution)) / half_resolution - QVector2D(1.0, 1.0);
+ QVector2D bottom_left = QVector2D(ValueToPixel(3, value, resolution)) / half_resolution - QVector2D(1.0, 1.0);
+
+ // Override default vertex coordinates.
+ QVector adjusted_vertices = {top_left.x(), top_left.y(), 0.0f,
+ top_right.x(), top_right.y(), 0.0f,
+ bottom_right.x(), bottom_right.y(), 0.0f,
+
+ top_left.x(), top_left.y(), 0.0f,
+ bottom_left.x(), bottom_left.y(), 0.0f,
+ bottom_right.x(), bottom_right.y(), 0.0f};
+ job.SetVertexCoordinates(adjusted_vertices);
+
+ // If no texture do nothing
+ if (!job.GetValue(kTextureInput).data().isNull()) {
+ // In the special case that all sliders are in their default position just
+ // push the texture.
+ if (!(job.GetValue(kTopLeftInput).data().value().isNull()
+ && job.GetValue(kTopRightInput).data().value().isNull() &&
+ job.GetValue(kBottomRightInput).data().value().isNull() &&
+ job.GetValue(kBottomLeftInput).data().value().isNull())) {
+ table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this);
+ } else {
+ table->Push(NodeValue::kTexture, job.GetValue(kTextureInput).data(), this);
+ }
+ }
+}
+
+ShaderCode CornerPinDistortNode::GetShaderCode(const QString &shader_id) const
+{
+ Q_UNUSED(shader_id)
+ return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/cornerpin.frag")),
+ FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/cornerpin.vert")));
+}
+
+QPointF CornerPinDistortNode::ValueToPixel(int value, const NodeValueRow& row, const QVector2D &resolution) const
+{
+ Q_ASSERT(value >= 0 && value <= 3);
+ switch (value) {
+ case 0: // Top left
+ return QPointF(row[kTopLeftInput].data().value().x(),
+ row[kTopLeftInput].data().value().y());
+ break;
+ case 1: // Top right
+ return QPointF(resolution.x() + row[kTopRightInput].data().value().x(),
+ row[kTopRightInput].data().value().y());
+ break;
+ case 2: // Bottom right
+ return QPointF(resolution.x() + row[kBottomRightInput].data().value().x(),
+ resolution.y() + row[kBottomRightInput].data().value().y());
+ break;
+ case 3: //Bottom left
+ return QPointF(row[kBottomLeftInput].data().value().x(),
+ row[kBottomLeftInput].data().value().y() + resolution.y());
+ break;
+ default: // We should never get here
+ return QPointF();
+ }
+}
+
+void CornerPinDistortNode::DrawGizmos(const NodeValueRow &row, const NodeGlobals &globals, QPainter *p)
+{
+ const QVector2D &resolution = globals.resolution();
+
+ const double handle_radius = GetGizmoHandleRadius(p->transform());
+
+ p->setPen(QPen(Qt::white, 0));
+
+ QPointF top_left = ValueToPixel(0, row, resolution);
+ QPointF top_right = ValueToPixel(1, row, resolution);
+ QPointF bottom_right = ValueToPixel(2, row, resolution);
+ QPointF bottom_left = ValueToPixel(3, row, resolution);
+
+ // Add the correct offset to each slider
+ SetInputProperty(kTopLeftInput, QStringLiteral("offset"), QVector2D(0.0, 0.0));
+ SetInputProperty(kTopRightInput, QStringLiteral("offset"), QVector2D(resolution.x() , 0.0));
+ SetInputProperty(kBottomRightInput, QStringLiteral("offset"), resolution);
+ SetInputProperty(kBottomLeftInput, QStringLiteral("offset"), QVector2D(0.0, resolution.y()));
+
+ // Draw bounding box
+ p->drawLine(QLineF(top_left, top_right));
+ p->drawLine(QLineF(top_right, bottom_right));
+ p->drawLine(QLineF(bottom_right, bottom_left));
+ p->drawLine(QLineF(bottom_left, top_left));
+
+ // Create handles
+ gizmo_resize_handle_[0] = CreateGizmoHandleRect(top_left, handle_radius);
+ gizmo_resize_handle_[1] = CreateGizmoHandleRect(top_right, handle_radius);
+ gizmo_resize_handle_[2] = CreateGizmoHandleRect(bottom_right, handle_radius);
+ gizmo_resize_handle_[3] = CreateGizmoHandleRect(bottom_left, handle_radius);
+
+ // Draw handles
+ DrawAndExpandGizmoHandles(p, handle_radius, gizmo_resize_handle_, kGizmoCornerCount);
+}
+
+bool CornerPinDistortNode::GizmoPress(const NodeValueRow &row, const NodeGlobals &globals, const QPointF &p)
+{
+ bool gizmo_active[kGizmoCornerCount] = {false};
+
+ for (int i = 0; i < kGizmoCornerCount; i++) {
+ gizmo_active[i] = gizmo_resize_handle_[i].contains(p);
+
+ if (gizmo_active[i]) {
+ gizmo_drag_start_ = p;
+ gizmo_res_ = globals.resolution();
+ gizmo_drag_ = i;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void CornerPinDistortNode::GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers)
+{
+ if (gizmo_dragger_.isEmpty()) {
+ gizmo_dragger_.resize(2);
+ if (gizmo_drag_ == 0) {
+ gizmo_dragger_[0].Start(NodeKeyframeTrackReference(NodeInput(this, kTopLeftInput), 0), time);
+ gizmo_dragger_[1].Start(NodeKeyframeTrackReference(NodeInput(this, kTopLeftInput), 1), time);
+ }
+ if (gizmo_drag_ == 1) {
+ gizmo_dragger_[0].Start(NodeKeyframeTrackReference(NodeInput(this, kTopRightInput), 0), time);
+ gizmo_dragger_[1].Start(NodeKeyframeTrackReference(NodeInput(this, kTopRightInput), 1), time);
+ }
+ if (gizmo_drag_ == 2) {
+ gizmo_dragger_[0].Start(NodeKeyframeTrackReference(NodeInput(this, kBottomRightInput), 0), time);
+ gizmo_dragger_[1].Start(NodeKeyframeTrackReference(NodeInput(this, kBottomRightInput), 1), time);
+ }
+ if (gizmo_drag_ == 3) {
+ gizmo_dragger_[0].Start(NodeKeyframeTrackReference(NodeInput(this, kBottomLeftInput), 0), time);
+ gizmo_dragger_[1].Start(NodeKeyframeTrackReference(NodeInput(this, kBottomLeftInput), 1), time);
+ }
+ }
+
+ QPointF diff = p - gizmo_drag_start_;
+ gizmo_dragger_[0].Drag(gizmo_dragger_[0].GetStartValue().toDouble() + diff.x());
+ gizmo_dragger_[1].Drag(gizmo_dragger_[1].GetStartValue().toDouble() + diff.y());
+}
+
+void CornerPinDistortNode::GizmoRelease(MultiUndoCommand *command) {
+ for (NodeInputDragger &i : gizmo_dragger_) {
+ i.End(command);
+ }
+ gizmo_dragger_.clear();
+}
+
+}
diff --git a/app/node/distort/cornerpin/cornerpindistortnode.h b/app/node/distort/cornerpin/cornerpindistortnode.h
new file mode 100644
index 000000000..6261d7e1a
--- /dev/null
+++ b/app/node/distort/cornerpin/cornerpindistortnode.h
@@ -0,0 +1,109 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2021 Olive Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+***/
+
+#ifndef CORNERPINDISTORTNODE_H
+#define CORNERPINDISTORTNODE_H
+
+#include
+
+#include "node/inputdragger.h"
+#include "node/node.h"
+
+namespace olive {
+class CornerPinDistortNode : public Node
+{
+ Q_OBJECT
+public:
+ CornerPinDistortNode();
+
+ NODE_DEFAULT_DESTRUCTOR(CornerPinDistortNode)
+
+ virtual Node* copy() const override
+ {
+ return new CornerPinDistortNode();
+ }
+
+ virtual QString Name() const override
+ {
+ return tr("Corner Pin");
+ }
+
+ virtual QString id() const override
+ {
+ return QStringLiteral("org.olivevideoeditor.Olive.cornerpin");
+ }
+
+ virtual QVector Category() const override
+ {
+ return {kCategoryDistort};
+ }
+
+ virtual QString Description() const override
+ {
+ return tr("Distort the image by dragging the corners.");
+ }
+
+ virtual void Retranslate() override;
+
+ virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
+
+ virtual ShaderCode GetShaderCode(const QString &shader_id) const override;
+
+ virtual bool HasGizmos() const override
+ {
+ return true;
+ }
+
+ virtual void DrawGizmos(const NodeValueRow& row, const NodeGlobals &globals, QPainter *p) override;
+
+ virtual bool GizmoPress(const NodeValueRow& row, const NodeGlobals &globals, const QPointF &p) override;
+ virtual void GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) override;
+ virtual void GizmoRelease(MultiUndoCommand *command) override;
+
+ /**
+ * @brief Convenience function - converts the 2D slider values from being
+ * an offset to the actual pixel value.
+ */
+ QPointF ValueToPixel(int value, const NodeValueRow &row, const QVector2D &resolution) const;
+
+ static const QString kTextureInput;
+ static const QString kPerspectiveInput;
+ static const QString kTopLeftInput;
+ static const QString kTopRightInput;
+ static const QString kBottomRightInput;
+ static const QString kBottomLeftInput;
+
+private:
+ // Gizmo variables
+ static const int kGizmoCornerCount = 4;
+ QRectF gizmo_resize_handle_[kGizmoCornerCount];
+ QRectF gizmo_whole_rect_;
+
+ int gizmo_drag_;
+ QVector gizmo_dragger_;
+ QPointF gizmo_drag_start_;
+ QVector2D gizmo_res_;
+
+};
+
+}
+
+
+#endif // CORNERPINDISTORTNODE_H
diff --git a/app/node/factory.cpp b/app/node/factory.cpp
index 7afcec6d9..ed28bc535 100644
--- a/app/node/factory.cpp
+++ b/app/node/factory.cpp
@@ -29,6 +29,7 @@
#include "block/subtitle/subtitle.h"
#include "block/transition/crossdissolve/crossdissolvetransition.h"
#include "block/transition/diptocolor/diptocolortransition.h"
+#include "distort/cornerpin/cornerpindistortnode.h"
#include "distort/crop/cropdistortnode.h"
#include "distort/flip/flipdistortnode.h"
#include "distort/transform/transformdistortnode.h"
@@ -264,6 +265,8 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id)
return new NoiseGeneratorNode();
case kTimeOffsetNode:
return new TimeOffsetNode();
+ case kCornerPinDistort:
+ return new CornerPinDistortNode();
case kInternalNodeCount:
break;
diff --git a/app/node/factory.h b/app/node/factory.h
index 5e627841c..77fe474eb 100644
--- a/app/node/factory.h
+++ b/app/node/factory.h
@@ -66,6 +66,7 @@ public:
kFlipDistort,
kNoiseGenerator,
kTimeOffsetNode,
+ kCornerPinDistort,
// Count value
kInternalNodeCount
diff --git a/app/render/job/shaderjob.h b/app/render/job/shaderjob.h
index 3bbbac19d..77a08ae9f 100644
--- a/app/render/job/shaderjob.h
+++ b/app/render/job/shaderjob.h
@@ -22,6 +22,7 @@
#define SHADERJOB_H
#include
+#include
#include "generatejob.h"
#include "render/texture.h"
@@ -87,6 +88,16 @@ public:
interpolation_.insert(id, interp);
}
+ void SetVertexCoordinates(const QVector &vertex_coords)
+ {
+ vertex_overrides_ = vertex_coords;
+ }
+
+ const QVector& GetVertexCoordinates()
+ {
+ return vertex_overrides_;
+ }
+
private:
QString shader_id_;
@@ -96,6 +107,8 @@ private:
QHash interpolation_;
+ QVector vertex_overrides_;
+
};
}
diff --git a/app/render/opengl/openglrenderer.cpp b/app/render/opengl/openglrenderer.cpp
index 37e43e88d..7aa0955cb 100644
--- a/app/render/opengl/openglrenderer.cpp
+++ b/app/render/opengl/openglrenderer.cpp
@@ -569,7 +569,13 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video
QOpenGLBuffer vert_vbo_;
vert_vbo_.create();
vert_vbo_.bind();
- vert_vbo_.allocate(blit_vertices.constData(), blit_vertices.size() * sizeof(GLfloat));
+ // If the job has vertex coordinate overrides use them instead of the defaults.
+ if (!job.GetVertexCoordinates().isEmpty()) {
+ Q_ASSERT(job.GetVertexCoordinates().size() == 18);
+ vert_vbo_.allocate(job.GetVertexCoordinates().constData(), job.GetVertexCoordinates().size() * sizeof(float));
+ } else {
+ vert_vbo_.allocate(blit_vertices.constData(), blit_vertices.size() * sizeof(GLfloat));
+ }
vert_vbo_.release();
QOpenGLBuffer frag_vbo_;
@@ -886,7 +892,11 @@ GLuint OpenGLRenderer::CompileShader(GLenum type, const QString &code)
QStringLiteral("#version 120\n\n");
#endif
- QString complete_code = shader_preamble;
+ QString complete_code;
+
+ if (!code.startsWith(QStringLiteral("#version"))) {
+ complete_code = shader_preamble;
+ }
if (code.isEmpty()) {
// Use default code
diff --git a/app/shaders/cornerpin.frag b/app/shaders/cornerpin.frag
new file mode 100644
index 000000000..7dd7e3306
--- /dev/null
+++ b/app/shaders/cornerpin.frag
@@ -0,0 +1,48 @@
+// Input texture
+uniform sampler2D ove_maintex;
+uniform sampler2D tex_in;
+uniform bool perspective_in;
+
+// Input texture coordinate
+varying vec2 ove_texcoord;
+
+varying vec2 q;
+varying vec2 b1;
+varying vec2 b2;
+varying vec2 b3;
+
+float Wedge2D(vec2 v, vec2 w) {
+ return (v.x*w.y) - (v.y*w.x);
+}
+
+void main() {
+ if(perspective_in){
+ gl_FragColor = texture2D(tex_in, ove_texcoord);
+ } else {
+ float A = Wedge2D(b2, b3);
+ float B = Wedge2D(b3, q) - Wedge2D(b1, b2);
+ float C = Wedge2D(b1, q);
+
+ vec2 uv;
+
+ // solve for v
+ if (abs(A) < 0.001) {
+ uv.y = -C/B;
+ } else {
+ float discrim = B*B - 4.0*A*C;
+ uv.y = 0.5 * (-B + sqrt(discrim)) / A;
+ }
+
+ // solve for u
+ vec2 denom = b1 + uv.y * b3;
+ if (abs(denom.x) > abs(denom.y)) {
+ uv.x = (q.x - b2.x * uv.y) / denom.x;
+ } else {
+ uv.x = (q.y - b2.y * uv.y) / denom.y;
+ }
+
+ uv.y = 1.0 - uv.y;
+
+ gl_FragColor = texture2D(tex_in, uv);
+ }
+}
diff --git a/app/shaders/cornerpin.vert b/app/shaders/cornerpin.vert
new file mode 100644
index 000000000..21c2b0527
--- /dev/null
+++ b/app/shaders/cornerpin.vert
@@ -0,0 +1,101 @@
+#version 130
+
+uniform bool perspective_in;
+uniform vec2 top_left_in;
+uniform vec2 top_right_in;
+uniform vec2 bottom_left_in;
+uniform vec2 bottom_right_in;
+
+uniform vec2 resolution_in;
+
+uniform mat4 ove_mvpmat;
+
+attribute vec4 a_position;
+attribute vec2 a_texcoord;
+
+varying vec2 ove_texcoord;
+
+varying vec2 q;
+varying vec2 b1;
+varying vec2 b2;
+varying vec2 b3;
+
+void main() {
+ // The slider inputs only contain the amount they have changed rather than
+ // their pixel locations so we adjust them here.
+ vec2 t_l = top_left_in;
+ vec2 t_r = top_right_in + vec2(resolution_in.x, 0.0);
+ vec2 b_r = bottom_right_in + resolution_in;
+ vec2 b_l = bottom_left_in + vec2(0.0, resolution_in.y);
+
+ gl_Position = ove_mvpmat * a_position;
+
+ if (perspective_in){
+ // Find the center of the quadrilateral by finding where the two diagonals intersect.
+ // https://www.reedbeta.com/blog/quadrilateral-interpolation-part-1/
+
+ // Here we calculate the gradient and constant (y = mx + c) for each diagonal.
+ float m1 = (t_r.y - b_l.y)/(t_r.x - b_l.x);
+ float c1 = b_l.y - m1 * b_l.x;
+ float m2 = (b_r.y - t_l.y)/(b_r.x - t_l.x);
+ float c2 = t_l.y - m2 * t_l.x;
+
+ // Find the intersection by setting the two line equations equal and rearrange.
+ float mid_x = (c2 - c1) / (m1 - m2);
+ float mid_y = m1 * mid_x + c1;
+
+ // Find the distance from each corner to our center point
+ float d0 = length(vec2(mid_x - b_l.x, mid_y - b_l.y));
+ float d1 = length(vec2(b_r.x - mid_x, mid_y - b_r.y));
+ float d2 = length(vec2(t_r.x - mid_x, t_r.y - mid_y));
+ float d3 = length(vec2(mid_x - t_l.x, t_l.y - mid_y));
+
+ float q = 1.0;
+
+ /*
+ Vertex IDs (aspect ratio irrelevant):
+ 0_____1
+ 3|\ |
+ | \ |
+ | \ |
+ | \ |
+ |____\|2
+ 4 5
+ */
+
+ if (gl_VertexID == 0 || gl_VertexID == 3) {
+ q = (d1+d3)/d3;
+ } else if (gl_VertexID == 1) {
+ q = (d0+d2)/d2;
+ } else if (gl_VertexID == 2 || gl_VertexID == 5) {
+ q = (d3+d1)/d1;
+ } else {
+ q = (d2+d0)/d0;
+ }
+
+ gl_Position[0] *= q;
+ gl_Position[1] *= q;
+ gl_Position[3] = q;
+ } else{
+ // https://www.reedbeta.com/blog/quadrilateral-interpolation-part-2/
+ vec2 pos;
+
+ if (gl_VertexID == 0 || gl_VertexID == 3) { // top left
+ pos = t_l;
+ } else if (gl_VertexID == 1) { // top right
+ pos = t_r;
+ } else if (gl_VertexID == 2 || gl_VertexID == 5) { // bottom right
+ pos = b_r;
+ } else if (gl_VertexID == 4) { // bottom left
+ pos = b_l;
+ }
+
+ q = pos - b_l;
+ b1 = b_r - b_l;
+ b2 = t_l - b_l;
+ b3 = b_l - b_r - t_l + t_r;
+ }
+
+
+ ove_texcoord = a_texcoord;
+}