-1

I have created a small Raphael app to showcase my struggle.

I created four handles which can be moved. A 'sheet' is covering the entire screen except for the square between the 4 handles.

Whenever the handles are dragged the sheet is placed accordingly.

What ends up happening is that in certain situations, the sheet folds on itself.

It's best if you just see the fiddle. You'll get what I'm talking about

http://jsfiddle.net/8qtffq0s/

How can I avoid this?

Notice: The screen is white. The black part is the sheet, and the white part is a gap in the sheet and not the other way around.

   //raphael object
            var paper = Raphael(0, 0, 600, 600)
            //create 4 handles
            h1 = paper.circle(50, 50, 10).attr("fill","green")
            h2 = paper.circle(300, 50, 10).attr("fill", "blue")
            h3 = paper.circle(300, 300, 10).attr("fill", "yellow")
            h4 = paper.circle(50, 300, 10).attr("fill", "red")

            //create covering sheet          
            path = ["M", 0, 0, "L", 600, 0, 600, 600, 0, 600, 'z', "M", h1.attrs.cx, h1.attrs.cy,"L", h4.attrs.cx, h4.attrs.cy, h3.attrs.cx, h3.attrs.cy, h2.attrs.cx, h2.attrs.cy,'z']
            sheet = paper.path(path).attr({ "fill": "black", "stroke": "white" }).toBack()

            //keep starting position of each handle on dragStart
            var startX,startY
            function getPos(handle) {
                startX= handle.attrs.cx
                startY = handle.attrs.cy
            }
            //Redraw the sheet to match the new handle placing
            function reDrawSheet() {
                path = ["M", 0, 0, "L", 600, 0, 600, 600, 0, 600, 'z', "M", h1.attrs.cx, h1.attrs.cy, "L", h4.attrs.cx, h4.attrs.cy, h3.attrs.cx, h3.attrs.cy, h2.attrs.cx, h2.attrs.cy, 'z']
                sheet.attr("path",path)
            }
            //enable handle dragging
            h1.drag(function (dx, dy) {
                this.attr("cx", startX + dx)
                this.attr("cy", startY + dy)
                reDrawSheet()
            },
            function () {
                getPos(this)
            })
            h2.drag(function (dx, dy) {
                this.attr("cx", startX + dx)
                this.attr("cy", startY + dy)
                reDrawSheet()
            },
            function () {
                getPos(this)
            })
            h3.drag(function (dx, dy) {
                this.attr("cx", startX + dx)
                this.attr("cy", startY + dy)
                reDrawSheet()
            },
            function () {
                getPos(this)
            })
            h4.drag(function (dx, dy) {
                this.attr("cx", startX + dx)
                this.attr("cy", startY + dy)
                reDrawSheet()
            },
            function () {
                getPos(this)
            })

Update: I improved the function "reDrawSheet" so now it can classify the points on the strings as top left, bottom left, bottom right, and top right

This solved many of my problems, but in some cases the sheet still folds on it self.

new fiddle: http://jsfiddle.net/1kj06co4/

new code: function reDrawSheet() { //c stands for coordinates c = [{ x: h1.attrs.cx, y: h1.attrs.cy }, { x: h4.attrs.cx, y: h4.attrs.cy }, { x: h3.attrs.cx, y: h3.attrs.cy }, { x: h2.attrs.cx, y: h2.attrs.cy }]

                //arrange the 4 points by height
                c.sort(function (a, b) {
                    return a.y - b.y
                })

                //keep top 2 points
                cTop = [c[0], c[1]]

                //arrange them from left to right
                cTop.sort(function (a, b) {
                    return a.x - b.x
                })

                //keep bottom 2 points
                cBottom = [c[2], c[3]]
                //arrange them from left to right
                cBottom.sort(function (a, b) {
                    return a.x - b.x
                })

                //top left most point
                tl = cTop[0]
                //bottom left most point
                bl = cBottom[0]
                //top right most point
                tr = cTop[1]
                //bottom right most point
                br = cBottom[1]

                path = ["M", 0, 0, "L", 600, 0, 600, 600, 0, 600, 'z', "M", tl.x,tl.y, "L", bl.x,bl.y, br.x,br.y, tr.x,tr.y, 'z']
                sheet.attr("path",path)
            }

To make things super clear, this is what I'm trying to avoid:

enter image description here

Update 2:

I was able to avoid the vertices from crossing by checking which path out of the three possible paths is the shortest and choosing it.

To do so, I added a function that checks the distance between two points

                  function distance(a, b) {
                return Math.sqrt(Math.pow(b.x - a.x, 2) + (Math.pow(b.y - a.y, 2)))
            }

And altered the code like so:

function reDrawSheet() {
                    //c stands for coordinates
                    c = [{ x: h1.attrs.cx, y: h1.attrs.cy }, { x: h4.attrs.cx, y: h4.attrs.cy }, { x: h3.attrs.cx, y: h3.attrs.cy }, { x: h2.attrs.cx, y: h2.attrs.cy }]
                    //d stands for distance
                    d=distance
                    //get the distance of all possible paths
                    d1 = d(c[0], c[1]) + d(c[1], c[2]) + d(c[2], c[3]) + d(c[3], c[0])
                    d2 = d(c[0], c[2]) + d(c[2], c[3]) + d(c[3], c[1]) + d(c[1], c[0])
                    d3 = d(c[0], c[2]) + d(c[2], c[1]) + d(c[1], c[3]) + d(c[3], c[0])
                    //choose the shortest distance
                    if (d1 <= Math.min(d2, d3)) {
                        tl = c[0]
                        bl = c[1]
                        br = c[2]
                        tr = c[3]
                    }
                    else if (d2 <= Math.min(d1, d3)) {
                        tl = c[0]
                        bl = c[2]
                        br = c[3]
                        tr = c[1]
                    }
                    else if (d3 <= Math.min(d1, d2)) {
                        tl = c[0]
                        bl = c[2]
                        br = c[1]
                        tr = c[3]
                    }



                    path = ["M", 0, 0, "L", 600, 0, 600, 600, 0, 600, 'z', "M", tl.x,tl.y, "L", bl.x,bl.y, br.x,br.y, tr.x,tr.y, 'z']
                    sheet.attr("path",path)
                }

Now the line does not cross itself like the image I attached about, but the sheet "flips" so everything turns black.

You can see the path is drawn correctly to connect the for points by the white stroke, but it does not leave a gap

new fiddle: http://jsfiddle.net/1kj06co4/1/

Picture of problem: enter image description here

Michael Seltenreich
  • 3,013
  • 2
  • 29
  • 55
  • One solution that comes to mind is to begin with the convex hull of the 4 points. If the CH Is a triangle, insert the isolated point into the closest edge. If you want the maximum (or minimum) area solution, chose the edge to split accordingly. – Codie CodeMonkey Feb 04 '15 at 20:54

1 Answers1

0

So... the trouble is to tell the inside from the outside.

You need the following functions:

function sub(a, b) {
    return { x: a.x - b.x , y: a.y - b.y };
}

function neg(a) {
    return { x: -a.x , y: -a.y };
}

function cross_prod(a, b) {
    // 2D vecs, so z==0.
    // Therefore, x and y components are 0.
    // Return the only important result, z.
    return (a.x*b.y - a.y*b.x);
}

And then you need to do the following once you've found tl,tr,br, and bl:

tlr = sub(tr,tl);
tbl = sub(bl,tl);
brl = sub(bl,br);
btr = sub(tr,br);

cropTL = cross_prod(    tbl,     tlr );
cropTR = cross_prod(neg(tlr),neg(btr));
cropBR = cross_prod(    btr,     brl );
cropBL = cross_prod(neg(brl),neg(tbl));

cwTL = cropTL > 0;
cwTR = cropTR > 0;
cwBR = cropBR > 0;
cwBL = cropBL > 0;

if (cwTL) {
    tmp = tr;
    tr = bl;
    bl = tmp;
}
if (cwTR == cwBR && cwBR == cwBL && cwTR!= cwTL) {
    tmp = tr;
    tr = bl;
    bl = tmp;
}

My version of the fiddle is here. :) http://jsfiddle.net/1kj06co4/39/