
/*
	This script contains helper functions for demonstrating kaleidoscoping tessellations.
	It renders them using Illustrator.
	*/
{
	
var srcImagePath = "/Volumes/projects_ii/projects_ii/2009.08.01_k_article/illustrations/test_image_1.psd";



/*
	a Tesselation has
	TPoints[]
	TPolys[]
	TEdges[]

	a TPoly has
	TPoints[]
	TEdges[]
	
	a TPoint has
	x,y
	TPolys[]
	TEdges[]
	
	a TEdge has
	TPoints p1, p2 (exactly 2)
	TPolys[] (1 or 2, for a well-formed tessellation)
	*/


function tNewPoint(x,y)
{
	var pt = new Object();
	pt.x = x;
	pt.y = y;
	pt.polys = [];
	pt.edges = [];
	return pt;
}
function tNew()
{
	var t = new Object();
	t.points = [];
	t.polys = [];
	t.edges = [];
	return t;
}
function tAddPoint(t,x,y)
{
	var pt = tNewPoint(x,y);
	t.points.push(pt);
	return pt;
}

function tGetEdge(p1,p2,existingEdges)
{
	// sort the two points
	if(p1.x > p2.x || (p1.x == p2.x && p1.y > p2.y))
	{
		var p0 = p1;
		p1 = p2;
		p2 = p0;
	}
	// find an existing edge...
	var eCount = existingEdges.length;
	for(var i = 0; i < eCount; i++)
	{
		var ed = existingEdges[i];
		if(ed.p1 == p1 && ed.p2 == p2)
			return ed;
	}
	var ed = new Object();
	ed.p1 = p1;
	ed.p2 = p2;
	ed.polys = [];
	var eCount = existingEdges.length;
	existingEdges[eCount] = ed; // for some reason, "push" failed here
//	existingEdges.push[ed];
	p1.edges.push(ed);
	p2.edges.push(ed);
	return ed;
}

function tNewPoly(pts,existingEdges)
{
	var po = new Object();
	po.points = [];
	po.edges = [];
	var ptCount = pts.length;
	
	var p1 = pts[ptCount - 1]; // the last point
	for(var i = 0; i < ptCount; i++)
	{
		var pt = pts[i];
		po.points.push(pt);
		pt.polys.push(po);

		var ed = tGetEdge(p1,pt,existingEdges);
		ed.polys.push(po);
		po.edges.push(ed);
		
		p1 = pt;
	}

	var poCenter = tFindCenter(po);
	po.centerX = poCenter[0];
	po.centerY = poCenter[1];
	return po;
}
function tAddPoly(t,pts)
{
	// replace any point-indices with points
	for(var i = 0; i < pts.length; i++)
	{
		var pt = pts[i];
		if(typeof(pt) == "number")
			pts[i] = t.points[pt];
	}
	var po = tNewPoly(pts,t.edges);
	t.polys.push(po);
}


/*
	* Add a single edge to a drawing.
	*/
function tDrawEdge(g,ed,name)
{
	if(name == null)
		name = "edge";

	var pathItem = g.pathItems.add();
	pathItem.name = name;
	var points = pathItem.pathPoints; // brand new, empty

	addP(points,ed.p1.x,ed.p1.y);
	addP(points,ed.p2.x,ed.p2.y);
	pathItem.closed = false;
	pathItem.stroked = true;
	pathItem.strokeWidth = 1;
	return pathItem;
}

/*
	* Add a single edge to a drawing.
	*/
function tDrawPoly(g,po,name)
{
	if(name == null)
		name = "poly";

	var pathItem = g.pathItems.add();
	pathItem.name = name;
	var points = pathItem.pathPoints; // brand new, empty
	
	for(var i = 0; i < po.points.length; i++)
	{
		var pt = po.points[i];
		addP(points,pt.x,pt.y);
	}

	pathItem.closed = true;
	pathItem.strokeWidth = 0;
	pathItem.stroked = false;
	setFill(pathItem,1,0,0);
	return pathItem;
}


/*
	Calculate a center of a polygon, by averaging
	its vertices. return [x,y]
	*/
function tFindCenter(po)
{
	var x = 0;
	var y = 0;
	var k = po.points.length;
	for(var i = 0; i < k; i++)
	{
		var pt = po.points[i];
		x += pt.x;
		y += pt.y;
	}
	x /= k;
	y /= k;
	return [x,y];
}

function tNewPatch(x,y,rot,flipped)
{
	var pa = new Object();
	pa.x = x;
	pa.y = y;
	pa.rot = rot;
	pa.flipped = flipped;
	pa.scale = 1.0;
	return pa;
}

function tReflectPatch(pa,ed)
{
	var s1 = [ed.p1.x,ed.p1.y];
	var s2 = [ed.p2.x,ed.p2.y];
	var p = [pa.x,pa.y];
	p = reflectPointOnLineSegment(p,s1,s2)
	r = reflectAngleOnLineSegment(pa.rot,s1,s2);
	
	var paPrime = tNewPatch(p[0],p[1],r,!pa.flipped);
	paPrime.scale = pa.scale;
	return paPrime;
}

/*
	Given a patch, and a bounding poly, and the path to an image,
	draw that image using the patch coordinate for the image's center.
*/
function tDrawPatchImage(g,pa,po,imgPath,name)
{
	if(name == null)
		name = "patch";

	var clipped1 = g.groupItems.add();
	clipped1.name = "clipped";
	var f1 = File(imgPath);

	var p1 = clipped1.placedItems.add();
	p1.file = f1;
	var scale = 100 * pa.scale;
	if(pa.flipped)
		p1.resize(scale,-scale);
	else
		p1.resize(scale,scale);
	var imgPos = [pa.x - p1.width / 2,pa.y + p1.height / 2];
	p1.position = imgPos;
	p1.rotate (pa.rot, true, false, false,false,Transformation.CENTER);
	
	var c1 = clipped1.pathItems.add();
	for(var i = 0; i < po.points.length; i++)
	{
		var pt = po.points[i];
		addP(c1.pathPoints,pt.x,pt.y);
	}
	c1.strokeWidth = 0;
	c1.stroked = false;
	c1.clipping = true;
	p1.cilipped = true;
	clipped1.clipped = true;
	return clipped1;
}






function tFindEdgeOtherPoly(ed,po)
{
	// actually, find the first poly that this edge is on, which isn't the given one.
	for(var i = 0; i < ed.polys.length; i++)
	{
		var poPrime = ed.polys[i];
		if(poPrime != po)
			return poPrime;
	}
	return null;
}

function tDrawAllEdges(g,t)
{
	for(var i = 0; i < t.edges.length; i++)
		tDrawEdge(g,t.edges[i],"e" + i);
}


/*
	Given a layer, a tessellation, a file, and a starting poly index,
	do a whole tessellation into the layer (image only, no edges)
	*/
function tDrawKTessellation(layer,t,imgFile,po)
{
	var poTodo = [po];
	while(poTodo.length > 0)
	{
		po = poTodo.pop();
		tDrawPatchImage(layer,po.pa,po,imgFile);
		// now, check all the edges, to queue new ones.
		for(var i = 0; i < po.edges.length; i++)
		{
			var ed = po.edges[i];
			var poPrime = tFindEdgeOtherPoly(ed,po);
			if(poPrime != null && poPrime.pa == null)
			{
				paPrime = tReflectPatch(po.pa,ed);
				poPrime.pa = paPrime;
				poTodo.push(poPrime);
			}
		}
	}
}



/*
	+-----------------------
	| Given a tessellation and a starting poly and a patch,
	| Propagate it across the mesh. If checkDeviation is passed,
	| Check the tessellation for consistency. (This is done by comparing the computed
	| rotation across an edge against the rotation already found
	| there, if any.) Each poly's .deviation field will contain
	| the greatest deviation found for that poly. Also, the overall
	| greatest deviation will be returned.
	|
	| Deviation is on a scale of 0 to 1 for a single rotation, and 2 to 3 for a rotation
	| AND a flipped parity. 0 to 1 is conceivably arithmetic error.
	| 2 to 3 is... a vertex with an odd number of edges, which cannot
	| ever be K=consistent.
	|
	*/
function tPropagateKTessellation(t,po,pa,checkDeviation)
{
	// first, clear all existing patches and deviations
	for(var i = 0; i < t.polys.length; i++)
	{
		var wPo = t.polys[i];
		wPo.pa = null;
		wPo.deviation = 0;
	}

	var resultDeviation = 0;

	// seed the search, which branches out across each edge...
	po.pa = pa; // stash the patch in the poly
	var poTodo = [po];
	while(poTodo.length > 0)
	{
		po = poTodo.pop();
		// check all the edges, to queue new ones.
		for(var i = 0; i < po.edges.length; i++)
		{
			var ed = po.edges[i];
			var poPrime = tFindEdgeOtherPoly(ed,po);
			if(poPrime != null)
			{
				if(poPrime.pa == null)
				{
					paPrime = tReflectPatch(po.pa,ed);
					poPrime.pa = paPrime;
					poTodo.push(poPrime);
				}
				else if(checkDeviation)
				{
					// this patch is already accounted for... and we've been
					// asked to check consistency. So, compute it, and see
					// how close it is. Track the result.
					paPrime = tReflectPatch(po.pa,ed);
					var paDiff = paPrime.rot - poPrime.pa.rot;
					paDiff = Math.abs(paDiff / 360); // circle = 1
					paDiff += (paPrime.flipped ^ poPrime.pa.flipped) ? 2 : 0; // parity? up by 2.
					poPrime.deviation = Math.max(poPrime.deviation,paDiff);
					resultDeviation = Math.max(resultDeviation,paDiff);
				}
			}
		}
	}
}

/*
	| Return [centerX,centerY] of all the points.
	| This is always computed by simple point xy averaging.
	*/
function tFindCenter(t)
{
	var cx = 0;
	var cy = 0;
	var ptCount = t.points.length;
	for(var i = 0; i < ptCount; i++)
	{
		var pt = t.points[i];
		cx += pt.x;
		cy += pt.y;
	}
	cx /= ptCount;
	cy /= ptCount;
	return [cx,cy];
}

function tOffsetAllPoints(t,x,y)
{
	for(var i = 0; i < t.points.length; i++)
	{
		var pt = t.points[i];
		pt.x += x;
		pt.y += y;
	}

	for(var i = 0; i < t.polys.length; i++)
	{
		var po = t.polys[i];
		po.centerX += x;
		po.centerY += y;
	}

}

function tCenterOnto(t,x,y)
{
	var cxy = tFindCenter(t);
	var cx = cxy[0];
	var cy = cxy[1];
	tOffsetAllPoints(t,x - cx,y - cy);
}

/*
	|
	| Return the poly whose center is closest to the center of the whole tessellation.
	| This is always computed by simple point xy averaging, and distance.
	*/
function tFindCenterPoly(t)
{
	var cxy = tFindCenter(t);
	var cx = cxy[0];
	var cy = cxy[1];
	
	// we have the tessellation center. Now find the poly which is nearest.
	var resultPo = null;
	var closest;
	var poCount = t.polys.length;
	for(var i = 0; i < poCount; i++)
	{
		var po = t.polys[i];
		var dx = po.centerX - cx;
		var dy = po.centerY - cy;
		var thisDist = Math.sqrt(dx * dx + dy * dy);
		if(resultPo == null || thisDist < closest)
		{
			resultPo = po;
			closest = thisDist;
		}
	}
	return resultPo;
}

/*
	| Draw a solid color on the poly, rgb 0..1 and opacity t 0..1.
	*/
function tDrawPoly(layer,po,r,g,b,t)
{
	var pathItem = layer.pathItems.add();
	var points = pathItem.pathPoints; // brand new, empty
	
	for(var i = 0; i < po.points.length; i++)
	{
		var pt = po.points[i];
		addP(points,pt.x,pt.y);
	}
	pathItem.closed = true;
	pathItem.stroked = false;
	setFill(pathItem,r,g,b,t);
	return pathItem;

}





function testT1()
{
	var t = tNew();
	var p1 = tAddPoint(t,0,0);
	var p2 = tAddPoint(t,100,0);
	var p3 = tAddPoint(t,100,100);
	var p4 = tAddPoint(t,0,100);
	var p5 = tAddPoint(t,50,200);
	var p6 = tAddPoint(t,200,150);
	var p7 = tAddPoint(t,150,250);
	var po1 = tAddPoly(t,[p1,p2,p3,p4]);
	var po2 = tAddPoly(t,[p3,p4,p5]);
	var po3 = tAddPoly(t,[p3,p5,p6]);
	var po4 = tAddPoly(t,[p5,p6,p7]);


	// try a grid
	t = tNew();
	var dim = 20;
	for(var i = 0; i < dim; i++)
	{
		var x = i * 40;
		for(var j = 0; j < dim; j++)
		{
			var y = j * 40;
			y += (j % 2) ^ (i % 2) ? -15 : 15;
			tAddPoint(t,x,y);
		}
	}

	for(var i = 0; i < dim - 1; i++)
		for(var j = 0; j < dim - 1; j++)
		{
			var k = i * dim + j;
			tAddPoly(t,[t.points[k],t.points[k + 1],t.points[k + dim + 1],t.points[k + dim]]);
		}
	
	
	alert("hi t.edges:" + t.edges.length + " t.polys:" + t.polys.length + " t.points:" + t.points.length);
	
	var doc = documents[0];
	var layer = doc.layers.add();
	layer.name = "edges";

	for(var i = 0; i < t.polys.length; i++)
	{
		var po = t.polys[i];
	//	tDrawPoly(layer,po,"p" + i);
		var xy = tFindCenter(po);
		var pa = tNewPatch(xy[0],xy[1],45,0);
		//tDrawPatchImage(layer,pa,po,srcImagePath,"pa" + i);
	}

	// try traversing the tessellation...
	var po = t.polys[0]; // first one.
	var xy = tFindCenter(po);
	var pa = tNewPatch(xy[0],xy[1],45,0);
	pa.scale = 1.3;
	po.pa = pa; // stash the patch in the poly
	var poTodo = [po];
	while(poTodo.length > 0)
	{
		po = poTodo.pop();
		tDrawPatchImage(layer,po.pa,po,srcImagePath);
		// now, check all the edges, to queue new ones.
		for(var i = 0; i < po.edges.length; i++)
		{
			var ed = po.edges[i];
			var poPrime = tFindEdgeOtherPoly(ed,po);
			if(poPrime != null && poPrime.pa == null)
			{
				paPrime = tReflectPatch(po.pa,ed);
				poPrime.pa = paPrime;
				poTodo.push(poPrime);
			}
		}
	}

	var edgeGroup = layer.groupItems.add();
	edgeGroup.name = "edges";
	tDrawAllEdges(edgeGroup,t);
}


function setFill(p,r,g,b,t)
{
	var co = new RGBColor();
	co.red = r * 255;
	co.green = g * 255;
	co.blue = b * 255;
	p.fillColor = co;
	if(t == null)
		t = 1.0;
	p.opacity = t * 100.0;
}

function addP(points,x,y)
{
	var p = points.add();
	p.anchor = [x,y];
	p.leftDirection = p.anchor;
	p.rightDirection = p.anchor;
	return p;
}


function layerByName(layers,name)
{
    var i;
    for(i = 0; i < layers.length; i++)
    {
        var layer = layers[i];
        if(layer.name == name)
            return layer;
    }
    return null;
}

function newLayer(doc,baseName)
{
	var n = 0;
	var result = null;
	while(result == null)
	{
		n++;
		var aName = "" + baseName + n;
		var existingLayer = layerByName(doc.layers,aName);
		if(existingLayer == null)
		{
			result = doc.layers.add();
			result.name = aName;
		}
	}
	return result;
}

function addTriangle(layer,a,b,c)
{
	var pathItem = layer.pathItems.add();
	var points = pathItem.pathPoints; // brand new, empty

	addP(points,a[0],a[1]);
	addP(points,b[0],b[1]);
	addP(points,c[0],c[1]);
	pathItem.closed = true;
	pathItem.strokeJoin = StrokeJoin.ROUNDENDJOIN;
	pathItem.strokeWidth = 1;
	return pathItem;
}

function reflectAngleOnLineSegment(r,s1,s2)
{
	var dx = s2[0] - s1[0];
	var dy = s2[1] - s1[1];
	var lineAngle = Math.atan2(dy,dx) * 57.29577951;
	
	r = 2 * lineAngle - r;
	return r;
}

function reflectPointOnLineSegment(p,s1,s2)
{
	var dx = s2[0] - s1[0];
	var dy = s2[1] - s1[1];
	
	var d2 = dx * dx + dy * dy;
	
	var a = (dx * dx - dy * dy) / d2;
	var b = 2 * dx * dy / d2;
	var c = b;
	var d = -a;
	
	var px = p[0];
	var py = p[1];
	px -= s1[0];
	py -= s1[1];
	
	var pxP = px * a + py * c;
	var pyP = px * b + py * d;

	// translate back
	px = pxP + s1[0];
	py = pyP + s1[1];
	
	return [px,py];
}


function addClippedImageInTriangle(g,imgFilePath,doFlip,imgPos,imgRot,tA,tB,tC)
{
	// now add a clipped image 
	var clipped1 = g.groupItems.add();
	clipped1.name = "clipped";
	var f1 = File(imgFilePath);

	var p1 = clipped1.placedItems.add();
	p1.file = f1;
	var corner = Transformation.TOPLEFT;
	if(doFlip)
	{
		p1.resize(100.0,-100.0);
		imgPos[1] += p1.height; // move it up, and work on the bottom left.
		corner = Transformation.BOTTOMLEFT;
	}
	p1.position = imgPos;
	p1.rotate (imgRot, true, false, false,false,corner);
	//p1.rotate(imgRot);
	var c1 = addTriangle(clipped1,tA,tB,tC);
	c1.strokeWidth = 0;
	c1.strokeColor = new NoColor();
	c1.clipping = true;
	p1.cilipped = true;
	clipped1.clipped = true;
	return clipped1;
}

function tryTwoTriangles(doc)
{
	var pA = [400,400];
	var pB = [600,430];
	var pC = [430,660];
	var pD = [480,300];

	var layer = newLayer(doc,"polys");
	addTriangle(layer,pA,pB,pC);
	addTriangle(layer,pA,pB,pD);

	var p = [460,380];
	var r = 25;
	var f = false;
	addClippedImageInTriangle(layer,srcImagePath,f,p,r,pA,pB,pD);
	p = reflectPointOnLineSegment(p,pA,pB);
	r = reflectAngleOnLineSegment(r,pA,pB);
	f = !f;
	addClippedImageInTriangle(layer,srcImagePath,f,p,r,pA,pB,pC);
// now add a clipped image 
//	var clipped1 = layer.groupItems.add();
//	clipped1.name = "clipped1";
//	var f1 = File(srcImagePath);

//	var p1 = clipped1.placedItems.add();
//	p1.file = f1;
//	p1.position = [500,500];
//	var c1 = addTriangle(clipped1,pA,pB,pC);
//	c1.strokeWidth = 0;
//	c1.strokeColor = new NoColor();
//	c1.clipping = true;
//	p1.cilipped = true;
//	clipped1.clipped = true;
}

// Creates a new raster item in a new document from a raster file
// jpgFilePath contains the full path and file name of a jpg file
function createRasterItem(doc,filePath,rot) 
{

	var layer = newLayer(doc,"clip");
	
	var g = layer.groupItems.add();
	g.name = "clipperG";

	


		var rasterFile = File(filePath);
		var myPlacedItem = g.placedItems.add();
		myPlacedItem.file = rasterFile;
		myPlacedItem.position = [400,400]; //Array( 0,0);

	var pathItem = g.pathItems.add();
	var points = pathItem.pathPoints; // brand new, empty

	addP(points,370,370);
	addP(points,300,400);
	addP(points,370,430);
	addP(points,400,500);
	addP(points,430,430);
	addP(points,500,400);
	addP(points,430,370);
	addP(points,400,300);
	
	pathItem.closed = true;
	
	pathItem.clipping = true;
myPlacedItem.clipped = true;
	
	g.clipped = true;
}


function tDrawTessellationImagesAndEdges(t,layerName,drawImages,drawEdges,imageFile,imageScale)
{
	
		// work on a new layer of the current doc.
		var doc = documents[0];
		var layer = doc.layers.add();
		layer.name = "tessellated_rectangles";
		layer.name = layerName;
		
		// center it
		var docCx = doc.width / 2;
		var docCy = doc.height / 2;
		tCenterOnto(t,docCx,docCy);

		var po = tFindCenterPoly(t);

		// seed the center and draw
		if(drawImages)
		{
			var g = layer.groupItems.add();
			g.name = "images";

			var pa = tNewPatch(po.centerX,po.centerY,0,0);
			pa.scale = imageScale;
			po.pa = pa; // stash the patch in the poly

			tDrawKTessellation(g,t,imageFile,po);
		}
		
		// draw the edges
		if(drawEdges)
		{
			var g = layer.groupItems.add();
			g.name = "edges";
			tDrawAllEdges(g,t);
		}
		
		// draw the center one
		tDrawPoly(layer,po,1,0,0,.3).name = "center_poly";

}


function main()
{
	var docRef = documents[0];
	var docHeight = docRef.height;
	var docWidth = docRef.width;
	
//	createRasterItem(docRef,srcImagePath,0);
	tryTwoTriangles(docRef);
}

//testT1();
//main();
}


