Skip to main content
Sturm359
Inspiring
July 28, 2014
Answered

How to check if a path fits inside of another path?

  • July 28, 2014
  • 1 reply
  • 4137 views

I've just been told that I need a script that will check and see if a path fits entirely within another path? In my case, I need to see if a GroupItem fits within a circle of a specific size. I don't see any methods available for PageItem that will do it for me, so does anyone know of a workaround? (Preferably without having to check every single PathPoint of every single item in the group.) I am using Illustrator CS6 and do my scripting with Javascript.

This topic has been closed for replies.
Correct answer Silly-V

I like the idea of using a Raycasting algorithm, as described in your link. It would work just perfectly for the ellipse/circle of which I speak. Now, the question is how to put said racasting algorithm into an ExtendScript script for Illustrator CS6?


This is good mind-exercise.
What I've come up with below will let you know whether your single path's entire set of anchor points is located wholly within a closed bezier path.

 

What you need to do to start working on it is make a document, make your containing ellipse and call it "mycir" and test out a path called "mypth".  The script gets a bezier interpolated point-polygon from the ellipse and uses the raycasting to check anchors of a test path to see if all are inside.  Of course you may already see the problem of parts of your curve which are not anchors overlapping the ellipse and not being detected.  If that's such a big problem, you will need to change the process to create the bezier polygon of your test path and check that with the raycasting.  And that's just for one path- to do an entire group, yes, you'll need to adapt it to work on a whole group of paths.  One more thing- this is really based on points.. so if your group has.. some effects, or appearance which really goes outside the boundaries, you'll definitely need to find a way to expand those to find your true boundaries, and raster effects may be a real deal-breaker.

#target illustrator

function test () {
	if (!Array.prototype.indexOf) {
		Array.prototype.indexOf = function(searchElement, fromIndex) {
			var k;
			if (this == null) {
				throw new TypeError('"this" is null or not defined');
			}
			var o = Object(this);
			var len = o.length >>> 0;
			if (len === 0) {
				return -1;
			}
			var n = +fromIndex || 0;
			if (Math.abs(n) === Infinity) {
				n = 0;
			}
			if (n >= len) {
				return -1;
			}
			k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
			while (k < len) {
				if (k in o && o[k] === searchElement) {
					return k;
				}
				k++;
			}
			return -1;
		};
	};

	function foreach (arr, func) {
		for (var i = 0; i < arr.length; i++) {
			func(arr[i]);
		}
	}

	function marker (xy, container) {
		var container = container || doc;
		var z = container.pathItems.ellipse(xy[1] + 2, xy[0] - 2, 4, 4);
		z.stroked = false;
		z.filled = true;
		z.fillColor = clr;
	}

	function getPathPoints (path) { // collects pathPoints of a path into segment arrays of 2 anchors & 2 controls between them
		var arr = [];
		for (var i = 0, ln = path.pathPoints.length; i < ln; i++) { // designed to work on closed paths!
			if (i < ln - 1) {
				var p = path.pathPoints[i], pnext = path.pathPoints[i + 1];
			} else {
				var p = path.pathPoints[i], pnext = path.pathPoints[0];
			}
			arr.push([p.anchor, p.rightDirection, pnext.leftDirection, pnext.anchor]); 
		}
		return arr;
	}

	function getBezierSegment (segArr) { // turns a pathPoint segment  (gotten with getPathPoints) into bezier interpolated points
		var anch2 = segArr[3], cont2 = segArr[2], cont1 = segArr[1], anch1 = segArr[0];
		var resultPts = [];
		var a = anch2[0] - 3 * (cont2[0]) + 3 * (cont1[0]) - anch1[0];
		var b = 3 * (cont2[0]) - 6 * (cont1[0]) + 3 * anch1[0];
		var c = 3 * (cont1[0]) - 3 * (anch1[0]);
		var d = anch1[0];
		var e = anch2[1] - 3 * (cont2[1]) + 3 * (cont1[1]) - anch1[1];
		var f = 3 * (cont2[1]) - 6 * (cont1[1]) + 3 * anch1[1];
		var g = 3 * (cont1[1]) - 3 * (anch1[1]);
		var h = anch1[1];
		var inc = 0.1;
		for (var t = 0; t < 1; t += inc) {
			resultPts.push([
				a * Math.pow(t, 3) + b * Math.pow(t, 2) + (c * t) + d,
				e * Math.pow(t, 3) + f * Math.pow(t, 2) + (g * t) + h
			]);
		}
		return resultPts;
	}

	function getBezierPath (pathPts) { // turns entire series of segments (gotten with getPathPoints)  into a complete set of interpolated bezier points
		var pathArr = [];
		for (var i = 0; i < pathPts.length; i++) {
			var thisPtSet = pathPts[i];
			var seg = getBezierSegment(thisPtSet);
			if (
				i > 0 && (seg[0][0] == pathArr[pathArr.length - 1][0] &&
				seg[0][1] == pathArr[pathArr.length - 1][1])
			) {
				seg.splice(0, 1);
			}
			pathArr = pathArr.concat(seg);
		}
		return pathArr;
	}

	function markBezierPoints (seg) {
		var grp = doc.groupItems.add();
		grp.name = "MyGroup";
		for (var i = 0; i < seg.length; i++) {
			marker(seg, grp);
		}
	}

	function makePolygon (pts) {
		var pth = doc.pathItems.add();
		pth.setEntirePath(pts);
		pth.filled = false;
		pth.stroked = true;
		pth.strokeColor = clr;
		pth.strokeWidth = 1;
	}

	var RayCaster = { // the whole raycasting through polygon algorithm from http://rosettacode.org/wiki/Ray-casting_algorithm
		// evaluates a single point to see if it's inside a polygon.
		Point : function (x, y) {
			this.x = x;
			this.y = y;
		},
		pointInPoly : function (point, poly) {
			var index, intersected, pointA, pointB, segment, segments;
			segments = (function () {
				var _i, _len, _results;
				_results = [];
				for (index = 0, _len = poly.length; index < _len; index++) {
					pointA = poly[index];
					pointB = poly[(index + 1) % poly.length];
					_results.push([new RayCaster.Point(pointA[0], pointA[1]), new RayCaster.Point(pointB[0], pointB[1])]);
				}
				return _results;
			})();
			intersected = (function () {
				var _i, _len, _results;
				_results = [];
				for (_i = 0, _len = segments.length; _i < _len; _i++) {
					segment = segments[_i];
					if (
						RayCaster.rayIntersectsSegment(new RayCaster.Point(point[0], point[1]), segment)
					) {
						_results.push(segment);
					}
				}
				return _results;
			})();
			return intersected.length % 2 !== 0;
		},
		rayIntersectsSegment : function (p, segment) {
			var a, b, mAB, mAP, p1, p2, _ref;
			p1 = segment[0], p2 = segment[1];
			_ref = p1.y < p2.y ? [p1, p2] : [p2, p1], a = _ref[0], b = _ref[1];
			if (p.y === b.y || p.y === a.y) {
				p.y += Number.MIN_VALUE;
			}
			if (p.y > b.y || p.y < a.y) {
				return false;
			} else if (p.x > a.x && p.x > b.x) {
				return false;
			} else if (p.x < a.x && p.x < b.x) {
				return true;
			} else {
				mAB = (b.y - a.y) / (b.x - a.x);
				mAP = (p.y - a.y) / (p.x - a.x);
				return mAP > mAB;
			}
		}
	};

	function pathAnchorsInPoly (myPath, polyPts) {
		var flag = false;
		var all = [];
		foreach(myPath.pathPoints, function (arg) {
			if (RayCaster.pointInPoly(arg.anchor, polyPts) === false) {
				all.push(false);
			}
			all.push(true);
		});
		flag = (all.indexOf(false) != -1) ? false : true;
		if (flag === false) {
			alert("NOT All Path Anchors are inside containing ellipse.");
		} else {
			alert("Yay!  All Path Anchors are INSIDE the containing ellipse!");
		}
		return flag; // for use later (?)
	}

	if (app == "[Application Adobe Illustrator]" && app.documents.length > 0) {
		app.coordinateSystem = CoordinateSystem.ARTBOARDCOORDINATESYSTEM;
		var clr = new CMYKColor();
		clr.cyan = 0;
		clr.magenta = 100;
		clr.yellow = 100;
		clr.black = 0;
		var doc = app.activeDocument;
		try {
			var m = doc.pathItems.getByName('mycir'); // my circle
			var mp = doc.pathItems.getByName('mypth'); // my test path
		} catch (e) {
			alert("Put 2 paths into a document: a containing ellipse, name it 'mycir', and some random path- name it 'mypth'.  Then run.");
			return;
		}
		var pts = getPathPoints(m);
		var bezPts = getBezierPath(pts);

		// testing functions:
		// markBezierPoints(bezPts);
		// makePolygon(bezPts);
		// foreach(bezPts, function(arg) {$.writeln(arg)});

		pathAnchorsInPoly(mp, bezPts);
	}
}

test();

 

1 reply

Monika Gause
Community Expert
Community Expert
July 28, 2014

This script can do it for rectangles:

https://forums.adobe.com/message/3699427#3699427

Maybe you can learn from it?

Sturm359
Sturm359Author
Inspiring
July 29, 2014

I'm afraid I do not see anything in there which can help with ellipses/circles. It uses the visibleBounds property of paths, which only gives you a top, bottom, left, and right. It's entirely possible for an object's visibleBounds to be within an ellipse's bounds, but still have parts of it sticking outside of the ellipse. Thus, Carlos' method will not work in my case, sorry.

I will say that, while looking at the DOM for Illustrator CS6, I noticed that there is an "inscribed" property (boolean, default true) that one can set when creating an ellipse through scripting. Any idea what that property actually means/does?

Sturm359
Sturm359Author
Inspiring
September 4, 2014

So, the "inscribed" property only sets how an ellipse is drawn. When you draw an ellipse in Illustrator, you drag your mouse from one corner to an opposite corner. This imaginary rectangle that you draw sets the boundary for the ellipse. By default, the ellipse is "inscribed" inside of that rectangle. The same thing happens with scripting. If you try to draw an ellipse with a script and set its "inscribed" property to false, then the ellipse will not be inside the rectangle, but outside of it; it will be essentially "circumscribed."

However, I'm afraid none of this helps with trying to discover whether or not one path fits wholly inside of an ellipse. Any ideas?