/* 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(); }