// Emacs settings: -*- mode: Fundamental; tab-width: 4; -*-//////////////////////////////////////////////////////////////////////////////                                                                        //// A web page for designing Celtic knots, implemented in Javascript       ////                                                                        //// Copyright 1998-2009, Andrew D. Birrell                                 ////                                                                        //// Javascript                                                             ////                                                                        //////////////////////////////////////////////////////////////////////////////// Assumes the following globals://   sqSize (pixels wide and high per square)//   cols (number of squares)//   rows (number of squares)//   skin (the choice of image: "wide", "narrow", or "tiny")//// Expects each square to be an IMG, with ID of the form "img-1-2"//// The globals are set, and the image array created, by a server-side PHP// script "knotwork.php".  See "knotwork-php.txt" for the source code.// In turn, "knotwork.php" was invoked by inclusion from "index.php" or// "large.php".  See "index-php.txt" for source./* The knot is built out of square images, in a rectangular array "cols"   squares by "rows" squares (both even).  The intersections occur at the   top-left corner of the squares [x,y] where x+y is odd.   The state of the intersections is held in the "cuts" array, indexed as if   there were cols/2+1 intersection in each row (even although there are   actually only cols/2 intersections in even rows).   Note that there's an asymmetry in our data structures between x and y   directions.  This is an arbitrary choice - we could have chosen to pack   the "cuts" array with either axis first.*/// The underlying image has the following shapes in its first row:var sh_TLBR = 0;    // shape: straight diagonal TLvar sh_BLTR = 1;    // shape: straight diagonal BLvar sh_TB = 2;      // shape: straight top -> bottomvar sh_LR = 3;      // shape: straight left -> rightvar sh_TLB = 4;     // shape: curve TL -> bottomvar sh_TLR = 5;     // shape: curve TL -> rightvar sh_BRT = 6;     // shape: curve BR -> topvar sh_BRL = 7;     // shape: curve BR -> leftvar sh_BLT = 8;     // shape: curve BL -> topvar sh_BLR = 9;     // shape: curve BL -> rightvar sh_TRB = 10;    // shape: curve TR -> bottomvar sh_TRL = 11;    // shape: curve TR -> leftvar sh_TL = 12;     // shape: arc top -> leftvar sh_BR = 13;     // shape: arc bottom -> rightvar sh_BL = 14;     // shape: arc bottom -> leftvar sh_TR = 15;     // shape: arc top -> right  // The second row is variants that go underneath at intersectionsvar sh_uTLBR = 16;  // shape: straight diagonal TLvar sh_uBLTR = 17;  // shape: straight diagonal BLvar sh_uTB = 18;    // shape: straight top -> bottomvar sh_uLR = 19;    // shape: straight left -> rightvar sh_uTLB = 20;   // shape: curve TL -> bottomvar sh_uTLR = 21;   // shape: curve TL -> rightvar sh_uBRT = 22;   // shape: curve BR -> topvar sh_uBRL = 23;   // shape: curve BR -> leftvar sh_uBLT = 24;   // shape: curve BL -> topvar sh_uBLR = 25;   // shape: curve BL -> rightvar sh_uTRB = 26;   // shape: curve TR -> bottomvar sh_uTRL = 27;   // shape: curve TR -> leftvar sh_uTL = 28;    // shape: arc top -> leftvar sh_uBR = 29;    // shape: arc bottom -> rightvar sh_uBL = 30;    // shape: arc bottom -> leftvar sh_uTR = 31;    // shape: arc top -> right// State of cutsvar cut_none = 0;   // no cut, normal intersectionvar cut_hor = 1;    // horizontal cutvar cut_ver = 2;    // vertical cut/* The cut points are at top-left of the squares [x,y] where x+y is odd.     The state of cut/intersection points is held in the "cuts" array,   indexed as if there were cols/2+1 entries per row (even although   there are actually 1 less in even rows).        Note that there's an asymmetry in our data structures between x and y   directions.  This is an arbitrary choice - we could have chosen to pack   the "cuts" array with either axis first.*/var xCuts;              // cuts per rowvar cuts = new Array(); // the state of play/* The over-under rule is that lines going diagonally down-right pass   underneath in even rows, and over in odd rows.  This rule always   produces properly woven knots.   We treat the cells in groups of four, named by their position in the   following diagram, where the square at the top-left is [x,y] with x   and y both even, and where "o" is an intersection or cut point. 	 + -- o -- +	 | TL | TR |	 |    |    |	 o -- + -- o	 | BL | BR |	 |    |    |	 + -- o -- +    We use the following arrays to determine which image to display in each   square.  If the square is a TL square (x and y both even), we use an   entry from the "tlShapes" array; a TR square uses the "trShapes" array,   etc.   The arrays are indexed by the value of the cuts adjacent to the given   square.  The index is the adjacent even-row cut plus 3 * the adjacent   odd-row cut. This deals completely with shape of thread fragment, and   with the over-under choice.*/              /* none     hor     ver */var tlShapes = [sh_BLTR,  sh_uBLR, sh_uBLT, /* none */                sh_TRL,   sh_LR,   sh_TL,   /* hor  */                sh_TRB,   sh_BR,   sh_TB];  /* ver  */var trShapes = [sh_uTLBR, sh_BRL,  sh_BRT,  /* none */                sh_uTLR,  sh_LR,   sh_TR,   /* hor  */                sh_uTLB,  sh_BL,   sh_TB];  /* ver  */var blShapes = [sh_TLBR,  sh_TLR,  sh_TLB,  /* none */                sh_uBRL,  sh_LR,   sh_BL,   /* hor  */                sh_uBRT,  sh_TR,   sh_TB];  /* ver  */var brShapes = [sh_uBLTR, sh_uTRL, sh_uTRB, /* none */                sh_BLR,   sh_LR,   sh_BR,   /* hor  */                sh_BLT,   sh_TL,   sh_TB];  /* ver  */function computeShape(x, y) {	// Compute appropriate shape for square [x,y], looking at adjacent cuts	var evenRow = cuts[Math.floor((y+1)/2) * 2 * xCuts + Math.floor(x/2)];	var oddRow = cuts[(Math.floor(y/2)*2+1) * xCuts + Math.floor((x+1)/2)];	var elt = document.getElementById("img-" + x + "-" + y);	var shapes = y%2==0 ? ( x%2==0 ? tlShapes : trShapes ) :						 ( x%2==0 ? blShapes : brShapes );	elt.src = "image.php?img=" + skin + "&n=" + shapes[evenRow + oddRow*3] +		 "&w=" + sqSize;}function getElementPos(element) {	// Return (x,y) for top-left of the given element, relative to document	var res = Object();	res.x = 0;	res.y = 0;	for (var obj = element; obj.offsetParent; obj = obj.offsetParent) {		res.x += obj.offsetLeft;		res.y += obj.offsetTop;	}	return res;}function getMousePos(event) {	// Return (x,y) for mouse coordinates, relative to the document	// Thanks to quirksmode.org for browser-specific details	var mouse = Object();	if (event.pageX) {		mouse.x = event.pageX;		mouse.y = event.pageY;	} else {		mouse.x = event.clientX + document.body.scrollLeft +			document.documentElement.scrollLeft;		mouse.y = event.clientY + document.body.scrollTop +			document.documentElement.scrollTop;	}	return mouse;}function clickImg(event) {	// Respond to click by letting the user adjust the cut-points	// Round to index of square whose TL the click was closest to.	var tl = getElementPos(document.getElementById("img-0-0"));	if (!event) event = window.event; // IE versus the rest	var mouse = getMousePos(event);	var xCoord = mouse.x - tl.x;	var yCoord = mouse.y - tl.y;	var x = Math.floor((xCoord + Math.floor(sqSize/2)) / sqSize);	var y = Math.floor((yCoord + Math.floor(sqSize/2)) / sqSize);	if ( (x+y)%2 == 1 && x > 0 && x < cols && y > 0 && y < rows) {		/* (x+y) is odd, so the closest square is a mutable cut point */		var cutIndex = y * xCuts + Math.floor(x/2); // index of cut point		cuts[cutIndex] = (cuts[cutIndex] + 1) % 3;		computeShape(x-1, y-1);		computeShape(x, y-1);		computeShape(x-1, y);		computeShape(x, y);	}	return false;  }function init() {	// Initialize the knot to a simple weave - no cuts except at perimeter	xCuts = Math.floor(cols/2) + 1;	var n;	for (n = 0; n < (rows+1)*xCuts; n++) {		cuts[n] = cut_none;	}	/* Place fixed cuts around the perimeter */	var y;	for (y = 0; y <= rows; y++) {		if (y%2 == 1) {			// cut at left and right edges in odd rows			var x0 = y * xCuts;			cuts[x0] = cut_ver;			cuts[x0+xCuts-1] = cut_ver;	 	}	}	var xLast = rows * xCuts;	var x;	for (x = 0; x < xCuts; x++) {		cuts[x] = cut_hor;		cuts[xLast+x] = cut_hor;	}	/* Set the initial shapes */	for (y = 0; y < rows; y++) {		for (x = 0; x < cols; x++) {			computeShape(x,y);		}	}}
