Clipper lib CL import, missing functionality?

ClipperVL.zip (37.0 KB)
As an exercise for myself to get more familiar with VL i tried to import the c sharp clipper port from here: http://www.angusj.com/delphi/clipper/documentation/Docs/Overview/_Body.htm

I imported via “Dependencies → Files → AddExisting…” whiched worked out of the box, as there where three new clipper categories shown in the node browser.

I managed fairly quickly to patch an offset node, as the “OffsetPolygons”-node generated from the imported dll takes its point data via a “MutableList<MutableList>” Datatype. For IntPoint (X and Y Coordinates as Ints) there are create and set nodes, so no problems there.

However the “execute”-node (which processes all the boolean operations) works different, i takes a “Clipper” datatype as an input for all the path data.

I have three problems there:

-I can create a clipper datatype an add paths to it via “AddPolygon” nodes. The problem is, that these “AddPolygon” nodes output its data as ClipperBase datatype, which is according to the clipper documentation a “ClipperBase** is an abstract base class for Clipper”: ClipperBase
Im not able to connect this datatype to the “execute”-node directly and have no idea how to extract “Clipper” from the “ClipperBase”-Datatype.

-The “Execute”-Node outputs a “Clipper”-Datatype. I havent found any nodes in the imported library to extract subtypes(Like “IntPoint”) from the “Clipper”-Datatype. So i cannot acess the pointcoordinates encapsulated in clipper.

-The Execute “Node” has a “Solution” Input! Pin which takes “MutableList<MutableList>” as datatype, but no outputpin, which somehow doesnt make sense to me.

I attached my patches in this post, check “Execute.v4p”

In case it helps here is my C# code for boolean operations node in VVVV:

#region usings
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;

using VVVV.PluginInterfaces.V1;
using VVVV.PluginInterfaces.V2;
using VVVV.Nodes;

using ClipperLib;

using VVVV.Core.Logging;
#endregion usings

namespace VVVV.Nodes
{
    using Path = List<IntPoint>;
    using Paths = List<List<IntPoint>>;

    public abstract class BooleanShapeCombinationNode : IPluginEvaluate
    {
        #region fields & pins
        [Input("Subject Paths")]
        public IDiffSpread<Path> FInput1;

        [Input("Clip Paths")]
        public IDiffSpread<Path> FInput2;

        [Input("Subject FillType")]
        public IDiffSpread<PolyFillType> FSubjFillType;

        [Input("Clip FillType")]
        public IDiffSpread<PolyFillType> FClipFillType;

        [Output("Output")]
        public ISpread<Path> FOutput;

        [Import()]
        public ILogger FLogger;

        #endregion fields & pins

        protected abstract ClipType GetClipType();
        //called when data for any output pin is requested
        private bool InputsChanged()
        {
            bool changed =
                FInput1.IsChanged
                || FInput2.IsChanged
                || FSubjFillType.IsChanged
                || FClipFillType.IsChanged;
            return changed;
        }
        public void Evaluate(int SpreadMax)
        {

            if ( InputsChanged() )
            {
                //TODO: fix broken binsize logic
                // ?? To past self: what exactly is broken ??
                Paths subj = new Paths(FInput1.SliceCount);

                foreach (Path p in FInput1)
                {
                    if (p != null)
                    {
                        subj.Add(p);
                    }
                }

                Paths clip = new Paths(FInput2.SliceCount);

                foreach (Path p in FInput2)
                {
                    if (p != null)
                    {
                        clip.Add(p);
                    }
                }

                if ((subj.Count >= 1) && (clip.Count >= 1))
                {
                    Paths solution = new Paths();

                    Clipper c = new Clipper();
                    c.AddPaths(subj, PolyType.ptSubject, true);
                    c.AddPaths(clip, PolyType.ptClip, true);

                    c.Execute(GetClipType(), solution,
                        FSubjFillType[0], FClipFillType[0]);

                    FOutput.AssignFrom(solution);
                }
                else if (subj.Count >= 1)
                {
                    FOutput.AssignFrom(subj);
                }
                else if (clip.Count >= 1)
                {
                    FOutput.AssignFrom(clip);
                }
                else {
                    // FOutput empty because no valid inputs
                    FOutput.SliceCount = 0;
                }

            }
        }
    }

    #region PluginInfo
    [PluginInfo(Name = "Union",
        Category = "ClipperPath",
        Version = "2D",
        Help = "Union of paths",
        Tags = "boolean")]
    #endregion PluginInfo
    public class ClipperPath2DValueUnionNode : BooleanShapeCombinationNode
    {
        protected override ClipType GetClipType()
        {
            return ClipType.ctUnion;
        }
    }

    #region PluginInfo
    [PluginInfo(Name = "Difference",
        Category = "ClipperPath",
        Version = "2D",
        Help = "Difference of paths",
        Tags = "boolean")]
    #endregion PluginInfo
    public class ClipperPath2DValueDifferenceNode : BooleanShapeCombinationNode
    {
        protected override ClipType GetClipType()
        {
            return ClipType.ctDifference;
        }
    }

    #region PluginInfo
    [PluginInfo(Name = "Intersection",
        Category = "ClipperPath",
        Version = "2D",
        Help = "Intersection of paths",
        Tags = "boolean")]
    #endregion PluginInfo
    public class ClipperPath2DValueIntersectionNode : BooleanShapeCombinationNode
    {
        protected override ClipType GetClipType()
        {
            return ClipType.ctIntersection;
        }
    }

    #region PluginInfo
    [PluginInfo(Name = "Xor",
        Category = "ClipperPath",
        Version = "2D",
        Help = "XOr of paths",
        Tags = "boolean")]
    #endregion PluginInfo
    public class ClipperPath2DValueXorNode : BooleanShapeCombinationNode
    {
        protected override ClipType GetClipType()
        {
            return ClipType.ctXor;
        }
    }

}

With the core code involving Execute is:

                    Paths solution = new Paths();

                    Clipper c = new Clipper();
                    c.AddPaths(subj, PolyType.ptSubject, true);
                    c.AddPaths(clip, PolyType.ptClip, true);

                    c.Execute(GetClipType(), solution,
                        FSubjFillType[0], FClipFillType[0]);

Where subj and clip have type Paths (which is alias for List<List>).

And GetClipType() returns a value of type ClipType for example ClipType.ctUnion

FSubjFillType[0] and FClipFillType[0] are of type PolyFillType

Unfortunately I can’t help with how you to do the same in VL

And yes Execute() takes a solution path list which it mutates and I don’t know if VL has any good way to deal with that since it goes against the idea of data flowing through nodes but it is fairly common working with C# code so maybe the devs have thought of something…?

vl doesn’t yet know the original input data type if you use an operation of a supertype. this is on our list and will work in the future. but for now you have two options:

  • put a CastAs node after the call of the inherited operation
  • surround with an IF region set to always true and pass the original input thru, this takes care of the execution order

thx.

so far so good. CastAs (and the if region) did help:

ClipperVL.zip (39.3 KB)

but how do i catch the solution after the mutation?? or better, how i can get “execute” to trigger the mutation on the solution… oh man thats confusing…

ahhhh lange leitung… got it working. thx tonfilm. some questions arised for that mutability thing but i will do that in a separate thread.

just a quick question/wild guess: passing the mutable list through an if region, creates a copy of that mutable list on the output of the region? so the original mutable list doesnt have to be mutated?

for those following: check the updated patch
ClipperVL.zip (39.8 KB)

the library works mostly with pointers and references which is a bit tricky to handle as data flow. for the solution you need to provide an existing mutable list that the Execute node will fill up. so create one on Create and hand that to the solver:

image

some execution ordering later you can scale the points and pass it out as spread of spread.

VVVV.Value.Clipper.vl (70.4 KB)

no, there is no magic going on, if you pass a reference thru an IF region you just get the same reference back.

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.