summaryrefslogtreecommitdiff
path: root/src/player/VectorNode.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/player/VectorNode.cpp')
-rw-r--r--src/player/VectorNode.cpp524
1 files changed, 524 insertions, 0 deletions
diff --git a/src/player/VectorNode.cpp b/src/player/VectorNode.cpp
new file mode 100644
index 0000000..f484a3b
--- /dev/null
+++ b/src/player/VectorNode.cpp
@@ -0,0 +1,524 @@
+//
+// libavg - Media Playback Engine.
+// Copyright (C) 2003-2014 Ulrich von Zadow
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library 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
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// Current versions can be found at www.libavg.de
+//
+
+#include "VectorNode.h"
+
+#include "TypeDefinition.h"
+#include "OGLSurface.h"
+#include "Image.h"
+
+#include "../base/Exception.h"
+#include "../base/Logger.h"
+#include "../base/ScopeTimer.h"
+#include "../base/Exception.h"
+#include "../base/WideLine.h"
+#include "../base/GeomHelper.h"
+#include "../base/Triangle.h"
+#include "../base/ObjectCounter.h"
+
+#include "../graphics/VertexArray.h"
+#include "../graphics/Filterfliprgb.h"
+
+#include "../glm/gtx/norm.hpp"
+
+#include <iostream>
+#include <sstream>
+
+using namespace std;
+using namespace boost;
+
+namespace avg {
+
+void VectorNode::registerType()
+{
+ TypeDefinition def = TypeDefinition("vectornode", "node")
+ .addArg(Arg<string>("color", "FFFFFF", false, offsetof(VectorNode, m_sColorName)))
+ .addArg(Arg<float>("strokewidth", 1, false, offsetof(VectorNode, m_StrokeWidth)))
+ .addArg(Arg<UTF8String>("texhref", "", false, offsetof(VectorNode, m_TexHRef)))
+ .addArg(Arg<string>("blendmode", "blend", false,
+ offsetof(VectorNode, m_sBlendMode)))
+ ;
+ TypeRegistry::get()->registerType(def);
+}
+
+VectorNode::VectorNode(const ArgList& args)
+ : m_Transform(glm::mat4(0))
+{
+ m_pShape = ShapePtr(createDefaultShape());
+
+ ObjectCounter::get()->incRef(&typeid(*this));
+ m_TexHRef = args.getArgVal<UTF8String>("texhref");
+ setTexHRef(m_TexHRef);
+ m_sColorName = args.getArgVal<string>("color");
+ m_Color = colorStringToColor(m_sColorName);
+}
+
+VectorNode::~VectorNode()
+{
+ ObjectCounter::get()->decRef(&typeid(*this));
+}
+
+void VectorNode::connectDisplay()
+{
+ setDrawNeeded();
+ m_Color = colorStringToColor(m_sColorName);
+ Node::connectDisplay();
+ m_pShape->moveToGPU();
+ setBlendModeStr(m_sBlendMode);
+}
+
+void VectorNode::connect(CanvasPtr pCanvas)
+{
+ Node::connect(pCanvas);
+ checkReload();
+}
+
+void VectorNode::disconnect(bool bKill)
+{
+ if (bKill) {
+ m_pShape->discard();
+ } else {
+ m_pShape->moveToCPU();
+ }
+ Node::disconnect(bKill);
+}
+
+void VectorNode::checkReload()
+{
+ Node::checkReload(m_TexHRef, m_pShape->getImage());
+ if (getState() == Node::NS_CANRENDER) {
+ m_pShape->moveToGPU();
+ setDrawNeeded();
+ }
+}
+
+const UTF8String& VectorNode::getTexHRef() const
+{
+ return m_TexHRef;
+}
+
+void VectorNode::setTexHRef(const UTF8String& href)
+{
+ m_TexHRef = href;
+ checkReload();
+ setDrawNeeded();
+}
+
+void VectorNode::setBitmap(BitmapPtr pBmp)
+{
+ m_TexHRef = "";
+ m_pShape->setBitmap(pBmp);
+ setDrawNeeded();
+}
+
+const string& VectorNode::getBlendModeStr() const
+{
+ return m_sBlendMode;
+}
+
+void VectorNode::setBlendModeStr(const string& sBlendMode)
+{
+ m_sBlendMode = sBlendMode;
+ m_BlendMode = GLContext::stringToBlendMode(sBlendMode);
+}
+
+static ProfilingZoneID PrerenderProfilingZone("VectorNode::prerender");
+
+void VectorNode::preRender(const VertexArrayPtr& pVA, bool bIsParentActive,
+ float parentEffectiveOpacity)
+{
+ Node::preRender(pVA, bIsParentActive, parentEffectiveOpacity);
+ {
+ ScopeTimer timer(PrerenderProfilingZone);
+ VertexDataPtr pShapeVD = m_pShape->getVertexData();
+ if (m_bDrawNeeded) {
+ pShapeVD->reset();
+ Pixel32 color = getColorVal();
+ calcVertexes(pShapeVD, color);
+ m_bDrawNeeded = false;
+ }
+ if (isVisible()) {
+ m_pShape->setVertexArray(pVA);
+ }
+ }
+}
+
+void VectorNode::maybeRender(const glm::mat4& parentTransform)
+{
+ AVG_ASSERT(getState() == NS_CANRENDER);
+ if (isVisible()) {
+ m_Transform = parentTransform;
+ GLContext::getMain()->setBlendMode(m_BlendMode);
+ render();
+ }
+}
+
+static ProfilingZoneID RenderProfilingZone("VectorNode::render");
+
+void VectorNode::render()
+{
+ ScopeTimer timer(RenderProfilingZone);
+ float curOpacity = getEffectiveOpacity();
+ if (curOpacity > 0.01) {
+ m_pShape->draw(m_Transform, curOpacity);
+ }
+}
+
+void VectorNode::setColor(const string& sColor)
+{
+ if (m_sColorName != sColor) {
+ m_sColorName = sColor;
+ m_Color = colorStringToColor(m_sColorName);
+ m_bDrawNeeded = true;
+ }
+}
+
+const string& VectorNode::getColor() const
+{
+ return m_sColorName;
+}
+
+void VectorNode::setStrokeWidth(float width)
+{
+ if (width != m_StrokeWidth) {
+ m_bDrawNeeded = true;
+ m_StrokeWidth = width;
+ }
+}
+
+float VectorNode::getStrokeWidth() const
+{
+ return m_StrokeWidth;
+}
+
+Pixel32 VectorNode::getColorVal() const
+{
+ return m_Color;
+}
+
+GLContext::BlendMode VectorNode::getBlendMode() const
+{
+ return m_BlendMode;
+}
+
+VectorNode::LineJoin VectorNode::string2LineJoin(const string& s)
+{
+ if (s == "miter") {
+ return LJ_MITER;
+ } else if (s == "bevel") {
+ return LJ_BEVEL;
+ } else {
+ throw(Exception(AVG_ERR_UNSUPPORTED,
+ "Vector linejoin "+s+" not supported."));
+ }
+}
+
+string VectorNode::lineJoin2String(LineJoin lineJoin)
+{
+ switch(lineJoin) {
+ case LJ_MITER:
+ return "miter";
+ case LJ_BEVEL:
+ return "bevel";
+ default:
+ AVG_ASSERT(false);
+ return 0;
+ }
+}
+
+void VectorNode::setDrawNeeded()
+{
+ m_bDrawNeeded = true;
+}
+
+bool VectorNode::isDrawNeeded()
+{
+ return m_bDrawNeeded;
+}
+
+void VectorNode::calcPolyLineCumulDist(vector<float>& cumulDists,
+ const vector<glm::vec2>& pts, bool bIsClosed)
+{
+ cumulDists.clear();
+ cumulDists.reserve(pts.size());
+ if (!pts.empty()) {
+ vector<float> distances;
+ distances.reserve(pts.size());
+ float totalDist = 0;
+ for (unsigned i = 1; i < pts.size(); ++i) {
+ float dist = glm::length(pts[i] - pts[i-1]);
+ distances.push_back(dist);
+ totalDist += dist;
+ }
+ if (bIsClosed) {
+ float dist = glm::length(pts[pts.size()-1] - pts[0]);
+ distances.push_back(dist);
+ totalDist += dist;
+ }
+
+ float cumulDist = 0;
+ cumulDists.push_back(0);
+ for (unsigned i = 0; i < distances.size(); ++i) {
+ cumulDist += distances[i]/totalDist;
+ cumulDists.push_back(cumulDist);
+ }
+ }
+}
+
+void VectorNode::calcEffPolyLineTexCoords(vector<float>& effTC,
+ const vector<float>& tc, const vector<float>& cumulDist)
+{
+ if (tc.empty()) {
+ effTC = cumulDist;
+ } else if (tc.size() == cumulDist.size()) {
+ effTC = tc;
+ } else {
+ effTC.reserve(cumulDist.size());
+ effTC = tc;
+ float minGivenTexCoord = tc[0];
+ float maxGivenTexCoord = tc[tc.size()-1];
+ float maxCumulDist = cumulDist[tc.size()-1];
+ int baselineDist = 0;
+ for (unsigned i = tc.size(); i < cumulDist.size(); ++i) {
+ int repeatFactor = int(cumulDist[i]/maxCumulDist);
+ float effCumulDist = fmod(cumulDist[i], maxCumulDist);
+ while (cumulDist[baselineDist+1] < effCumulDist) {
+ baselineDist++;
+ }
+ float ratio = (effCumulDist-cumulDist[baselineDist])/
+ (cumulDist[baselineDist+1]-cumulDist[baselineDist]);
+ float rawTexCoord = (1-ratio)*tc[baselineDist] +ratio*tc[baselineDist+1];
+ float texCoord = rawTexCoord
+ +repeatFactor*(maxGivenTexCoord-minGivenTexCoord);
+ effTC.push_back(texCoord);
+ }
+ }
+
+}
+
+void VectorNode::calcPolyLine(const vector<glm::vec2>& origPts,
+ const vector<float>& origTexCoords, bool bIsClosed, LineJoin lineJoin,
+ const VertexDataPtr& pVertexData, Pixel32 color)
+{
+ vector<glm::vec2> pts;
+ pts.reserve(origPts.size());
+ vector<float> texCoords;
+ texCoords.reserve(origPts.size());
+
+ pts.push_back(origPts[0]);
+ texCoords.push_back(origTexCoords[0]);
+ for (unsigned i = 1; i < origPts.size(); ++i) {
+ if (glm::distance2(origPts[i], origPts[i-1]) > 0.1) {
+ pts.push_back(origPts[i]);
+ texCoords.push_back(origTexCoords[i]);
+ }
+ }
+ if (bIsClosed) {
+ texCoords.push_back(origTexCoords[origTexCoords.size()-1]);
+ }
+
+ int numPts = pts.size();
+
+ // Create array of wide lines.
+ vector<WideLine> lines;
+ lines.reserve(numPts-1);
+ for (int i = 0; i < numPts-1; ++i) {
+ lines.push_back(WideLine(pts[i], pts[i+1], m_StrokeWidth));
+ }
+ if (bIsClosed) {
+ lines.push_back(WideLine(pts[numPts-1], pts[0], m_StrokeWidth));
+ }
+ // First points
+ if (bIsClosed) {
+ WideLine lastLine = lines[lines.size()-1];
+ glm::vec2 pli = getLineLineIntersection(lastLine.pl0, lastLine.dir,
+ lines[0].pl0, lines[0].dir);
+ glm::vec2 pri = getLineLineIntersection(lastLine.pr0, lastLine.dir,
+ lines[0].pr0, lines[0].dir);
+ Triangle tri(lastLine.pl1, lines[0].pl0, pri);
+ if (tri.isClockwise()) {
+ if (!LineSegment(lastLine.pr0, lastLine.pr1).isPointOver(pri) &&
+ !LineSegment(lines[0].pr0, lines[0].pr1).isPointOver(pri))
+ {
+ pri = lines[0].pr1;
+ }
+ } else {
+ if (!LineSegment(lastLine.pl0, lastLine.pl1).isPointOver(pli) &&
+ !LineSegment(lines[0].pl0, lines[0].pl1).isPointOver(pli))
+ {
+ pli = lines[0].pl1;
+ }
+ }
+
+ float curTC = texCoords[0];
+ switch (lineJoin) {
+ case LJ_MITER:
+ pVertexData->appendPos(pli, glm::vec2(curTC,1), color);
+ pVertexData->appendPos(pri, glm::vec2(curTC,0), color);
+ break;
+ case LJ_BEVEL: {
+ if (tri.isClockwise()) {
+ pVertexData->appendPos(lines[0].pl0, glm::vec2(curTC,1), color);
+ pVertexData->appendPos(pri, glm::vec2(curTC,0), color);
+ } else {
+ pVertexData->appendPos(pli, glm::vec2(curTC,1), color);
+ pVertexData->appendPos(lines[0].pr0, glm::vec2(curTC,0), color);
+ }
+ }
+ break;
+ default:
+ AVG_ASSERT(false);
+ break;
+ }
+ } else {
+ pVertexData->appendPos(lines[0].pl0, glm::vec2(texCoords[0],1), color);
+ pVertexData->appendPos(lines[0].pr0, glm::vec2(texCoords[0],0), color);
+ }
+
+ // All complete line segments
+ unsigned numNormalSegments;
+ if (bIsClosed) {
+ numNormalSegments = pts.size();
+ } else {
+ numNormalSegments = pts.size()-2;
+ }
+ for (unsigned i = 0; i < numNormalSegments; ++i) {
+ const WideLine* pLine1 = &(lines[i]);
+ const WideLine* pLine2;
+ if (i == pts.size()-1) {
+ pLine2 = &(lines[0]);
+ } else {
+ pLine2 = &(lines[i+1]);
+ }
+ glm::vec2 pli = getLineLineIntersection(pLine1->pl0, pLine1->dir, pLine2->pl0,
+ pLine2->dir);
+ glm::vec2 pri = getLineLineIntersection(pLine1->pr0, pLine1->dir, pLine2->pr0,
+ pLine2->dir);
+ Triangle tri(pLine1->pl1, pLine2->pl0, pri);
+ if (tri.isClockwise()) {
+ if (!LineSegment(pLine1->pr0, pLine1->pr1).isPointOver(pri) &&
+ !LineSegment(pLine2->pr0, pLine2->pr1).isPointOver(pri))
+ {
+ pri = pLine2->pr1;
+ }
+ } else {
+ if (!LineSegment(pLine1->pl0, pLine1->pl1).isPointOver(pli) &&
+ !LineSegment(pLine2->pl0, pLine2->pl1).isPointOver(pli))
+ {
+ pli = pLine2->pl1;
+ }
+ }
+
+ int curVertex = pVertexData->getNumVerts();
+ float curTC = texCoords[i+1];
+ switch (lineJoin) {
+ case LJ_MITER:
+ pVertexData->appendPos(pli, glm::vec2(curTC,1), color);
+ pVertexData->appendPos(pri, glm::vec2(curTC,0), color);
+ pVertexData->appendQuadIndexes(
+ curVertex-1, curVertex-2, curVertex+1, curVertex);
+ break;
+ case LJ_BEVEL:
+ {
+ float TC0;
+ float TC1;
+ if (tri.isClockwise()) {
+ calcBevelTC(*pLine1, *pLine2, true, texCoords, i+1, TC0, TC1);
+ pVertexData->appendPos(pLine1->pl1, glm::vec2(TC0,1), color);
+ pVertexData->appendPos(pLine2->pl0, glm::vec2(TC1,1), color);
+ pVertexData->appendPos(pri, glm::vec2(curTC,0), color);
+ pVertexData->appendQuadIndexes(
+ curVertex-1, curVertex-2, curVertex+2, curVertex);
+ pVertexData->appendTriIndexes(
+ curVertex, curVertex+1, curVertex+2);
+ } else {
+ calcBevelTC(*pLine1, *pLine2, false, texCoords, i+1, TC0, TC1);
+ pVertexData->appendPos(pLine1->pr1, glm::vec2(TC0,0), color);
+ pVertexData->appendPos(pli, glm::vec2(curTC,1), color);
+ pVertexData->appendPos(pLine2->pr0, glm::vec2(TC1,0), color);
+ pVertexData->appendQuadIndexes(
+ curVertex-2, curVertex-1, curVertex+1, curVertex);
+ pVertexData->appendTriIndexes(
+ curVertex, curVertex+1, curVertex+2);
+ }
+ }
+ break;
+ default:
+ AVG_ASSERT(false);
+ }
+ }
+
+ // Last segment (PolyLine only)
+ if (!bIsClosed) {
+ int curVertex = pVertexData->getNumVerts();
+ float curTC = texCoords[numPts-1];
+ pVertexData->appendPos(lines[numPts-2].pl1, glm::vec2(curTC,1), color);
+ pVertexData->appendPos(lines[numPts-2].pr1, glm::vec2(curTC,0), color);
+ pVertexData->appendQuadIndexes(curVertex-1, curVertex-2, curVertex+1, curVertex);
+ }
+}
+
+void VectorNode::calcBevelTC(const WideLine& line1, const WideLine& line2,
+ bool bIsLeft, const vector<float>& texCoords, unsigned i,
+ float& TC0, float& TC1)
+{
+ float line1Len = line1.getLen();
+ float line2Len = line2.getLen();
+ float triLen;
+ if (bIsLeft) {
+ triLen = glm::length(line1.pl1 - line2.pl0);
+ } else {
+ triLen = glm::length(line1.pr1 - line2.pr0);
+ }
+ float ratio0 = line1Len/(line1Len+triLen/2);
+ TC0 = (1-ratio0)*texCoords[i-1]+ratio0*texCoords[i];
+ float nextTexCoord;
+ if (i == texCoords.size()-1) {
+ nextTexCoord = texCoords[i];
+ } else {
+ nextTexCoord = texCoords[i+1];
+ }
+ float ratio1 = line2Len/(line2Len+triLen/2);
+ TC1 = ratio1*texCoords[i]+(1-ratio1)*nextTexCoord;
+}
+
+int VectorNode::getNumDifferentPts(const vector<glm::vec2>& pts)
+{
+ int numPts = pts.size();
+ for (unsigned i=1; i<pts.size(); ++i) {
+ if (glm::distance2(pts[i], pts[i-1])<0.1) {
+ numPts--;
+ }
+ }
+ return numPts;
+}
+
+const glm::mat4& VectorNode::getTransform() const
+{
+ return m_Transform;
+}
+
+Shape* VectorNode::createDefaultShape() const
+{
+ return new Shape(MaterialInfo(GL_REPEAT, GL_CLAMP_TO_EDGE, false));
+}
+
+}