Source of “knotwork.js”.
285 lines, 9.9 KBytes.   Last modified 1:29 pm, 1st October 2016 PDT.
1 // Emacs settings: -*- mode: Fundamental; tab-width: 4; -*- 2 3 //////////////////////////////////////////////////////////////////////////// 4 // // 5 // A web page for designing Celtic knots, implemented in Javascript // 6 // // 7 // Copyright 1998-2016, Andrew D. Birrell // 8 // // 9 // Javascript // 10 // // 11 //////////////////////////////////////////////////////////////////////////// 12 13 "use strict"; 14 15 16 // Assumes the following globals: 17 // var sqSize (pixels wide and high per square) 18 // var cols (number of squares wide, assumed to be even) 19 // var rows (number of squares high, assumed to be even) 20 // var skin (the choice of image: "wide", "narrow", or "tiny") 21 // 22 // The playing area is an array of IMG elements, "cols" wide by "rows" high, 23 // each with ID of the form "img-x-y". 24 // 25 // The globals are set, and the image array is created, by a server-side PHP 26 // script "knotwork-php.txt". In turn, "knotwork.php" was invoked by 27 // inclusion from "index.php" or "large.php". 28 29 30 // The knotwork intersections occur at the top-left of the squares [x,y] 31 // where x+y is odd. 32 // 33 // The state of the intersections is held in the "cuts" array, indexed by 34 // x/2 + y * (cols/2 + 1) 35 36 var cut_none = 0; // no cut, threads just cross over 37 var cut_hor = 1; // horizontal cut 38 var cut_ver = 2; // vertical cut 39 40 var xCuts = Math.floor(cols/2) + 1; // intersections per row 41 var cuts = []; // the state of each intersection 42 43 44 // The image to be placed in each square is a sub-square of a single 45 // server-side image (per skin), obtained with a URL starting "image.php". 46 47 // The server-side image has the following shapes in its first row: 48 // 49 var sh_TLBR = 0; // straight diagonal TL -> BR 50 var sh_BLTR = 1; // straight diagonal BL -> TR 51 var sh_TB = 2; // straight top -> bottom 52 var sh_LR = 3; // straight left -> right 53 var sh_TLB = 4; // curve TL -> bottom 54 var sh_TLR = 5; // curve TL -> right 55 var sh_BRT = 6; // curve BR -> top 56 var sh_BRL = 7; // curve BR -> left 57 var sh_BLT = 8; // curve BL -> top 58 var sh_BLR = 9; // curve BL -> right 59 var sh_TRB = 10; // curve TR -> bottom 60 var sh_TRL = 11; // curve TR -> left 61 var sh_TL = 12; // arc top -> left 62 var sh_BR = 13; // arc bottom -> right 63 var sh_BL = 14; // arc bottom -> left 64 var sh_TR = 15; // arc top -> right 65 66 // The second row is variants that go underneath at intersections 67 // 68 var sh_uTLBR = 16; // straight diagonal TL -> BR 69 var sh_uBLTR = 17; // straight diagonal BL -> TR 70 var sh_uTB = 18; // straight top -> bottom 71 var sh_uLR = 19; // straight left -> right 72 var sh_uTLB = 20; // curve TL -> bottom 73 var sh_uTLR = 21; // curve TL -> right 74 var sh_uBRT = 22; // curve BR -> top 75 var sh_uBRL = 23; // curve BR -> left 76 var sh_uBLT = 24; // curve BL -> top 77 var sh_uBLR = 25; // curve BL -> right 78 var sh_uTRB = 26; // curve TR -> bottom 79 var sh_uTRL = 27; // curve TR -> left 80 var sh_uTL = 28; // arc top -> left 81 var sh_uBR = 29; // arc bottom -> right 82 var sh_uBL = 30; // arc bottom -> left 83 var sh_uTR = 31; // arc top -> right 84 85 86 /* The over-under rule is that lines going diagonally down-right pass 87 underneath at intersections [x,y] where y is even, and over where y is 88 odd. This rule always produces properly woven knots. 89 90 To compute the appropriate image for each square, we consider the squares 91 in groups of four, named by their position in the following diagram, 92 where the square at the top-left is [x,y] with x and y both even, and 93 where "o" is an intersection or cut point. 94 95 + -- o -- + 96 | TL | TR | even 97 | | | 98 o -- + -- o 99 | BL | BR | odd 100 | | | 101 + -- o -- + 102 even odd 103 104 The following arrays deliver the identity of the appropriate sub-square 105 of the server-side image for each square. 106 107 The arrays are indexed by the cut values of the two intersections at 108 corners of the given square. The index is the adjacent even-row cut plus 109 3 * the odd-row cut. This deals completely with shape of thread 110 fragment, and with the over-under choice. 111 */ 112 113 /* cut_none cut_hor cut_ver */ 114 var tlShapes = [sh_BLTR, sh_uBLR, sh_uBLT, /* cut_none */ 115 sh_TRL, sh_LR, sh_TL, /* cut_hor */ 116 sh_TRB, sh_BR, sh_TB]; /* cut_ver */ 117 var trShapes = [sh_uTLBR, sh_BRL, sh_BRT, /* cut_none */ 118 sh_uTLR, sh_LR, sh_TR, /* cut_hor */ 119 sh_uTLB, sh_BL, sh_TB]; /* cut_ver */ 120 var blShapes = [sh_TLBR, sh_TLR, sh_TLB, /* cut_none */ 121 sh_uBRL, sh_LR, sh_BL, /* cut_hor */ 122 sh_uBRT, sh_TR, sh_TB]; /* cut_ver */ 123 var brShapes = [sh_uBLTR, sh_uTRL, sh_uTRB, /* cut_none */ 124 sh_BLR, sh_LR, sh_BR, /* cut_hor */ 125 sh_BLT, sh_TL, sh_TB]; /* cut_ver */ 126 var tlbrShapes = [[tlShapes, trShapes], 127 [blShapes, brShapes]]; 128 129 function computeShape(x, y) { 130 // Compute appropriate shape for square [x,y], looking at adjacent cuts, 131 // and install the appropriate image. 132 // 133 var elt = document.getElementById("img-" + x + "-" + y); 134 var evenCut = cuts[Math.floor((y+1)/2)*2 * xCuts + Math.floor(x/2)]; 135 var oddCut = cuts[(Math.floor(y/2)*2+1) * xCuts + Math.floor((x+1)/2)]; 136 var shapes = tlbrShapes[y%2][x%2]; 137 elt.src = "image.php?img=" + skin + "&n=" + shapes[evenCut + oddCut*3]; 138 } 139 140 function changeSquare(x, y) { 141 // If (x+y) is odd and in-bounds, change the cut at that intersection 142 // and update the adjacent squares. 143 // 144 if ( (x+y)%2 == 1 && x > 0 && x < cols && y > 0 && y < rows) { 145 var cutIndex = y * xCuts + Math.floor(x/2); 146 cuts[cutIndex] = (cuts[cutIndex] + 1) % 3; 147 computeShape(x-1, y-1); 148 computeShape(x, y-1); 149 computeShape(x-1, y); 150 computeShape(x, y); 151 } 152 } 153 154 function initSquares() { 155 // Set each square's image according to the "cuts" array 156 // 157 for (var y = 0; y < rows; y++) { 158 for (var x = 0; x < cols; x++) { 159 computeShape(x,y); 160 } 161 } 162 } 163 164 165 // Functions from the adbabMisc.js library ... 166 167 function getElementPos(elt) { 168 // Return {x,y} for top-left of the element, relative to the document 169 // 170 var x = 0, y = 0; 171 for (var obj = elt; obj.offsetParent; obj = obj.offsetParent) { 172 x += obj.offsetLeft; 173 y += obj.offsetTop; 174 } 175 return {x: x, y: y}; 176 } 177 178 function getMousePos(event) { 179 // Return (x,y) for mouse coordinates, relative to the document 180 // 181 var x, y; 182 if (event.pageX !== undefined) { 183 x = event.pageX; 184 y = event.pageY; 185 } else if (document.body && document.documentElement) { 186 x = event.clientX + document.body.scrollLeft + 187 document.documentElement.scrollLeft; 188 y = event.clientY + document.body.scrollTop + 189 document.documentElement.scrollTop; 190 } 191 return {x: x, y: y}; 192 } 193 194 195 // Event handling and initialization ... 196 197 function demo() { 198 var dCuts = [ 199 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 200 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 201 2, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 2, 0, 202 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 203 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 204 2, 2, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 2, 2, 205 2, 2, 0, 0, 2, 0, 0, 2, 0, 0, 2, 0, 0, 2, 2, 0, 206 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 207 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 208 2, 2, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 2, 2, 209 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 210 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 211 2, 2, 0, 0, 2, 0, 0, 2, 0, 0, 2, 0, 0, 2, 2, 0, 212 2, 2, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 2, 2, 213 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 214 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 215 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 216 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 217 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 218 2, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 2, 219 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; 220 if (cols != 30 || rows != 20) { 221 alert("Incorrect call of \"demo\""); 222 return false; 223 } 224 for (var i = 0, len = dCuts.length; i < len; i++) cuts[i] = dCuts[i]; 225 initSquares(); 226 return false; 227 } 228 229 function clickImg(event) { 230 // Respond to click by changing the cut at the closest intersection 231 // 232 if (!event) event = window.event; // IE versus the rest 233 var tl = getElementPos(document.getElementById("img-0-0")); 234 var mouse = getMousePos(event); 235 var xCoord = mouse.x - tl.x; 236 var yCoord = mouse.y - tl.y; 237 var x = Math.floor(xCoord / sqSize); 238 var y = Math.floor(yCoord / sqSize); 239 var xOffset = xCoord - x * sqSize; 240 var yOffset = yCoord - y * sqSize; 241 // Click is inside square [x,y]; find closest intersection 242 if ((x + y) % 2 == 0) { 243 // closest is at top-right or bottom-left of this square 244 if (xOffset > yOffset) { // + -- o 245 x++; // | | 246 } else { // | | 247 y++; // o -- + 248 } 249 } else { 250 // closest is at top-left or bottom-right of this square 251 if (xOffset > sqSize - yOffset) { // o -- + 252 x++; // | | 253 y++; // | | 254 } // + -- o 255 } 256 // In all cases, x+y is now odd 257 changeSquare(x, y); 258 return false; 259 } 260 261 function init() { 262 // Initialize the knot to a simple weave - no cuts except at perimeter 263 // 264 var n; 265 for (n = 0; n < (rows+1)*xCuts; n++) { 266 cuts[n] = cut_none; 267 } 268 // Place fixed cuts around the perimeter 269 for (var y = 0; y <= rows; y++) { 270 if (y%2 == 1) { 271 // cut at left and right edges in odd rows 272 var x0 = y * xCuts; 273 cuts[x0] = cut_ver; 274 cuts[x0+xCuts-1] = cut_ver; 275 } 276 } 277 var xLast = rows * xCuts; 278 for (var x = 0; x < xCuts; x++) { 279 cuts[x] = cut_hor; 280 cuts[xLast+x] = cut_hor; 281 } 282 // Update the display 283 initSquares(); 284 return false; 285 }
End of listing