blob: e31a196975d1465639e163450f320394b4cb0c6e [file] [log] [blame]
caryclarkdac1d172014-06-17 05:15:38 -07001<!DOCTYPE html>
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00002
3<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
4<head>
5 <meta charset="utf-8" />
6 <title></title>
7<div style="height:0">
caryclark1049f122015-04-20 08:31:59 -07008
Cary Clarkd2eb5812017-01-18 11:00:57 -05009<div id="halfplane">
10ccwOf {{{380.294464,140.44487}, {379.597463,140.82048}}} id=1
11ccwOf {{{380.294495,140.444855}, {378.965302,141.103577}}} id=2
Cary Clark7eb01e02016-12-08 14:36:32 -050012</div>
caryclark34efb702016-10-24 08:19:06 -070013 </div>
14
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000015<script type="text/javascript">
16
caryclark34efb702016-10-24 08:19:06 -070017 var testDivs = [
Cary Clarkd2eb5812017-01-18 11:00:57 -050018 halfplane,
caryclarkb36a3cd2016-10-18 07:59:44 -070019 ];
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000020
caryclark54359292015-03-26 07:52:43 -070021 var decimal_places = 3;
22
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000023 var tests = [];
24 var testTitles = [];
25 var testIndex = 0;
26 var ctx;
caryclark54359292015-03-26 07:52:43 -070027
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000028 var subscale = 1;
29 var xmin, xmax, ymin, ymax;
30 var scale;
31 var initScale;
32 var mouseX, mouseY;
33 var mouseDown = false;
34 var srcLeft, srcTop;
35 var screenWidth, screenHeight;
36 var drawnPts;
37 var curveT = 0;
caryclark1049f122015-04-20 08:31:59 -070038 var curveW = -1;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000039
40 var lastX, lastY;
41 var activeCurve = [];
42 var activePt;
caryclark54359292015-03-26 07:52:43 -070043 var ids = [];
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000044
caryclark54359292015-03-26 07:52:43 -070045 var focus_on_selection = 0;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000046 var draw_t = false;
caryclark1049f122015-04-20 08:31:59 -070047 var draw_w = false;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000048 var draw_closest_t = false;
caryclarkfa6d6562014-07-29 12:13:28 -070049 var draw_cubic_red = false;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000050 var draw_derivative = false;
caryclark54359292015-03-26 07:52:43 -070051 var draw_endpoints = 2;
caryclark1049f122015-04-20 08:31:59 -070052 var draw_id = 0;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000053 var draw_midpoint = 0;
54 var draw_mouse_xy = false;
55 var draw_order = false;
56 var draw_point_xy = false;
57 var draw_ray_intersect = false;
58 var draw_quarterpoint = 0;
59 var draw_tangents = 1;
60 var draw_sortpoint = 0;
61 var retina_scale = !!window.devicePixelRatio;
62
63 function parse(test, title) {
64 var curveStrs = test.split("{{");
65 var pattern = /-?\d+\.*\d*e?-?\d*/g;
66 var curves = [];
67 for (var c in curveStrs) {
68 var curveStr = curveStrs[c];
caryclark54359292015-03-26 07:52:43 -070069 var idPart = curveStr.split("id=");
70 var id = -1;
71 if (idPart.length == 2) {
72 id = parseInt(idPart[1]);
73 curveStr = idPart[0];
74 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000075 var points = curveStr.match(pattern);
76 var pts = [];
77 for (var wd in points) {
78 var num = parseFloat(points[wd]);
79 if (isNaN(num)) continue;
80 pts.push(num);
81 }
caryclark54359292015-03-26 07:52:43 -070082 if (pts.length > 2) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000083 curves.push(pts);
caryclark54359292015-03-26 07:52:43 -070084 }
85 if (id >= 0) {
86 ids.push(id);
87 ids.push(pts);
88 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000089 }
90 if (curves.length >= 1) {
91 tests.push(curves);
92 testTitles.push(title);
93 }
94 }
95
96 function init(test) {
97 var canvas = document.getElementById('canvas');
98 if (!canvas.getContext) return;
99 ctx = canvas.getContext('2d');
100 var resScale = retina_scale && window.devicePixelRatio ? window.devicePixelRatio : 1;
101 var unscaledWidth = window.innerWidth - 20;
102 var unscaledHeight = window.innerHeight - 20;
103 screenWidth = unscaledWidth;
104 screenHeight = unscaledHeight;
105 canvas.width = unscaledWidth * resScale;
106 canvas.height = unscaledHeight * resScale;
107 canvas.style.width = unscaledWidth + 'px';
108 canvas.style.height = unscaledHeight + 'px';
109 if (resScale != 1) {
110 ctx.scale(resScale, resScale);
111 }
112 xmin = Infinity;
113 xmax = -Infinity;
114 ymin = Infinity;
115 ymax = -Infinity;
116 for (var curves in test) {
117 var curve = test[curves];
caryclark1049f122015-04-20 08:31:59 -0700118 var last = curve.length - (curve.length % 2 == 1 ? 1 : 0);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000119 for (var idx = 0; idx < last; idx += 2) {
120 xmin = Math.min(xmin, curve[idx]);
121 xmax = Math.max(xmax, curve[idx]);
122 ymin = Math.min(ymin, curve[idx + 1]);
123 ymax = Math.max(ymax, curve[idx + 1]);
124 }
125 }
caryclark54359292015-03-26 07:52:43 -0700126 xmin -= Math.min(1, Math.max(xmax - xmin, ymax - ymin));
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000127 var testW = xmax - xmin;
128 var testH = ymax - ymin;
129 subscale = 1;
130 while (testW * subscale < 0.1 && testH * subscale < 0.1) {
131 subscale *= 10;
132 }
133 while (testW * subscale > 10 && testH * subscale > 10) {
134 subscale /= 10;
135 }
136 setScale(xmin, xmax, ymin, ymax);
137 mouseX = (screenWidth / 2) / scale + srcLeft;
138 mouseY = (screenHeight / 2) / scale + srcTop;
139 initScale = scale;
140 }
141
142 function setScale(x0, x1, y0, y1) {
143 var srcWidth = x1 - x0;
144 var srcHeight = y1 - y0;
145 var usableWidth = screenWidth;
146 var xDigits = Math.ceil(Math.log(Math.abs(xmax)) / Math.log(10));
147 var yDigits = Math.ceil(Math.log(Math.abs(ymax)) / Math.log(10));
148 usableWidth -= (xDigits + yDigits) * 10;
149 usableWidth -= decimal_places * 10;
150 var hscale = usableWidth / srcWidth;
151 var vscale = screenHeight / srcHeight;
152 scale = Math.min(hscale, vscale);
153 var invScale = 1 / scale;
154 var sxmin = x0 - invScale * 5;
155 var symin = y0 - invScale * 10;
156 var sxmax = x1 + invScale * (6 * decimal_places + 10);
157 var symax = y1 + invScale * 10;
158 srcWidth = sxmax - sxmin;
159 srcHeight = symax - symin;
160 hscale = usableWidth / srcWidth;
161 vscale = screenHeight / srcHeight;
162 scale = Math.min(hscale, vscale);
163 srcLeft = sxmin;
164 srcTop = symin;
165 }
166
167function dxy_at_t(curve, t) {
168 var dxy = {};
169 if (curve.length == 6) {
170 var a = t - 1;
171 var b = 1 - 2 * t;
172 var c = t;
173 dxy.x = a * curve[0] + b * curve[2] + c * curve[4];
174 dxy.y = a * curve[1] + b * curve[3] + c * curve[5];
caryclark1049f122015-04-20 08:31:59 -0700175 } else if (curve.length == 7) {
176 var p20x = curve[4] - curve[0];
177 var p20y = curve[5] - curve[1];
178 var p10xw = (curve[2] - curve[0]) * curve[6];
179 var p10yw = (curve[3] - curve[1]) * curve[6];
180 var coeff0x = curve[6] * p20x - p20x;
181 var coeff0y = curve[6] * p20y - p20y;
182 var coeff1x = p20x - 2 * p10xw;
183 var coeff1y = p20y - 2 * p10yw;
184 dxy.x = t * (t * coeff0x + coeff1x) + p10xw;
185 dxy.y = t * (t * coeff0y + coeff1y) + p10yw;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000186 } else if (curve.length == 8) {
187 var one_t = 1 - t;
188 var a = curve[0];
189 var b = curve[2];
190 var c = curve[4];
191 var d = curve[6];
192 dxy.x = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
193 a = curve[1];
194 b = curve[3];
195 c = curve[5];
196 d = curve[7];
197 dxy.y = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
198 }
199 return dxy;
200}
201
202 var flt_epsilon = 1.19209290E-07;
203
204 function approximately_zero(A) {
205 return Math.abs(A) < flt_epsilon;
206 }
207
208 function approximately_zero_inverse(A) {
209 return Math.abs(A) > (1 / flt_epsilon);
210 }
211
212 function quad_real_roots(A, B, C) {
213 var s = [];
214 var p = B / (2 * A);
215 var q = C / A;
216 if (approximately_zero(A) && (approximately_zero_inverse(p)
217 || approximately_zero_inverse(q))) {
218 if (approximately_zero(B)) {
219 if (C == 0) {
220 s[0] = 0;
221 }
222 return s;
223 }
224 s[0] = -C / B;
225 return s;
226 }
227 /* normal form: x^2 + px + q = 0 */
228 var p2 = p * p;
229 if (!approximately_zero(p2 - q) && p2 < q) {
230 return s;
231 }
232 var sqrt_D = 0;
233 if (p2 > q) {
234 sqrt_D = Math.sqrt(p2 - q);
235 }
236 s[0] = sqrt_D - p;
237 var flip = -sqrt_D - p;
238 if (!approximately_zero(s[0] - flip)) {
239 s[1] = flip;
240 }
241 return s;
242 }
243
244 function cubic_real_roots(A, B, C, D) {
245 if (approximately_zero(A)) { // we're just a quadratic
246 return quad_real_roots(B, C, D);
247 }
248 if (approximately_zero(D)) { // 0 is one root
249 var s = quad_real_roots(A, B, C);
250 for (var i = 0; i < s.length; ++i) {
251 if (approximately_zero(s[i])) {
252 return s;
253 }
254 }
255 s.push(0);
256 return s;
257 }
258 if (approximately_zero(A + B + C + D)) { // 1 is one root
259 var s = quad_real_roots(A, A + B, -D);
260 for (var i = 0; i < s.length; ++i) {
261 if (approximately_zero(s[i] - 1)) {
262 return s;
263 }
264 }
265 s.push(1);
266 return s;
267 }
268 var a, b, c;
269 var invA = 1 / A;
270 a = B * invA;
271 b = C * invA;
272 c = D * invA;
273 var a2 = a * a;
274 var Q = (a2 - b * 3) / 9;
275 var R = (2 * a2 * a - 9 * a * b + 27 * c) / 54;
276 var R2 = R * R;
277 var Q3 = Q * Q * Q;
278 var R2MinusQ3 = R2 - Q3;
279 var adiv3 = a / 3;
280 var r;
281 var roots = [];
282 if (R2MinusQ3 < 0) { // we have 3 real roots
283 var theta = Math.acos(R / Math.sqrt(Q3));
284 var neg2RootQ = -2 * Math.sqrt(Q);
285 r = neg2RootQ * Math.cos(theta / 3) - adiv3;
286 roots.push(r);
287 r = neg2RootQ * Math.cos((theta + 2 * Math.PI) / 3) - adiv3;
288 if (!approximately_zero(roots[0] - r)) {
289 roots.push(r);
290 }
291 r = neg2RootQ * Math.cos((theta - 2 * Math.PI) / 3) - adiv3;
292 if (!approximately_zero(roots[0] - r) && (roots.length == 1
293 || !approximately_zero(roots[1] - r))) {
294 roots.push(r);
295 }
296 } else { // we have 1 real root
297 var sqrtR2MinusQ3 = Math.sqrt(R2MinusQ3);
298 var A = Math.abs(R) + sqrtR2MinusQ3;
299 A = Math.pow(A, 1/3);
300 if (R > 0) {
301 A = -A;
302 }
303 if (A != 0) {
304 A += Q / A;
305 }
306 r = A - adiv3;
307 roots.push(r);
308 if (approximately_zero(R2 - Q3)) {
309 r = -A / 2 - adiv3;
caryclark1049f122015-04-20 08:31:59 -0700310 if (!approximately_zero(roots[0] - r)) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000311 roots.push(r);
312 }
313 }
314 }
315 return roots;
316 }
317
318 function approximately_zero_or_more(tValue) {
319 return tValue >= -flt_epsilon;
320 }
321
322 function approximately_one_or_less(tValue) {
323 return tValue <= 1 + flt_epsilon;
324 }
325
326 function approximately_less_than_zero(tValue) {
327 return tValue < flt_epsilon;
328 }
329
330 function approximately_greater_than_one(tValue) {
331 return tValue > 1 - flt_epsilon;
332 }
333
334 function add_valid_ts(s) {
335 var t = [];
336 nextRoot:
337 for (var index = 0; index < s.length; ++index) {
338 var tValue = s[index];
339 if (approximately_zero_or_more(tValue) && approximately_one_or_less(tValue)) {
340 if (approximately_less_than_zero(tValue)) {
341 tValue = 0;
342 } else if (approximately_greater_than_one(tValue)) {
343 tValue = 1;
344 }
345 for (var idx2 = 0; idx2 < t.length; ++idx2) {
346 if (approximately_zero(t[idx2] - tValue)) {
347 continue nextRoot;
348 }
349 }
350 t.push(tValue);
351 }
352 }
353 return t;
354 }
355
356 function quad_roots(A, B, C) {
357 var s = quad_real_roots(A, B, C);
358 var foundRoots = add_valid_ts(s);
359 return foundRoots;
360 }
361
362 function cubic_roots(A, B, C, D) {
363 var s = cubic_real_roots(A, B, C, D);
364 var foundRoots = add_valid_ts(s);
365 return foundRoots;
366 }
367
368 function ray_curve_intersect(startPt, endPt, curve) {
369 var adj = endPt[0] - startPt[0];
370 var opp = endPt[1] - startPt[1];
371 var r = [];
caryclark1049f122015-04-20 08:31:59 -0700372 var len = (curve.length == 7 ? 6 : curve.length) / 2;
373 for (var n = 0; n < len; ++n) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000374 r[n] = (curve[n * 2 + 1] - startPt[1]) * adj - (curve[n * 2] - startPt[0]) * opp;
375 }
376 if (curve.length == 6) {
377 var A = r[2];
378 var B = r[1];
379 var C = r[0];
380 A += C - 2 * B; // A = a - 2*b + c
381 B -= C; // B = -(b - c)
382 return quad_roots(A, 2 * B, C);
383 }
caryclark1049f122015-04-20 08:31:59 -0700384 if (curve.length == 7) {
385 var A = r[2];
386 var B = r[1] * curve[6];
387 var C = r[0];
388 A += C - 2 * B; // A = a - 2*b + c
389 B -= C; // B = -(b - c)
390 return quad_roots(A, 2 * B, C);
391 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000392 var A = r[3]; // d
393 var B = r[2] * 3; // 3*c
394 var C = r[1] * 3; // 3*b
395 var D = r[0]; // a
396 A -= D - C + B; // A = -a + 3*b - 3*c + d
397 B += 3 * D - 2 * C; // B = 3*a - 6*b + 3*c
398 C -= 3 * D; // C = -3*a + 3*b
399 return cubic_roots(A, B, C, D);
400 }
401
402 function x_at_t(curve, t) {
403 var one_t = 1 - t;
404 if (curve.length == 4) {
405 return one_t * curve[0] + t * curve[2];
406 }
407 var one_t2 = one_t * one_t;
408 var t2 = t * t;
409 if (curve.length == 6) {
410 return one_t2 * curve[0] + 2 * one_t * t * curve[2] + t2 * curve[4];
411 }
caryclark1049f122015-04-20 08:31:59 -0700412 if (curve.length == 7) {
413 var numer = one_t2 * curve[0] + 2 * one_t * t * curve[2] * curve[6]
414 + t2 * curve[4];
415 var denom = one_t2 + 2 * one_t * t * curve[6]
416 + t2;
417 return numer / denom;
418 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000419 var a = one_t2 * one_t;
420 var b = 3 * one_t2 * t;
421 var c = 3 * one_t * t2;
422 var d = t2 * t;
423 return a * curve[0] + b * curve[2] + c * curve[4] + d * curve[6];
424 }
425
426 function y_at_t(curve, t) {
427 var one_t = 1 - t;
428 if (curve.length == 4) {
429 return one_t * curve[1] + t * curve[3];
430 }
431 var one_t2 = one_t * one_t;
432 var t2 = t * t;
433 if (curve.length == 6) {
434 return one_t2 * curve[1] + 2 * one_t * t * curve[3] + t2 * curve[5];
435 }
caryclark1049f122015-04-20 08:31:59 -0700436 if (curve.length == 7) {
437 var numer = one_t2 * curve[1] + 2 * one_t * t * curve[3] * curve[6]
438 + t2 * curve[5];
439 var denom = one_t2 + 2 * one_t * t * curve[6]
440 + t2;
441 return numer / denom;
442 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000443 var a = one_t2 * one_t;
444 var b = 3 * one_t2 * t;
445 var c = 3 * one_t * t2;
446 var d = t2 * t;
447 return a * curve[1] + b * curve[3] + c * curve[5] + d * curve[7];
448 }
449
450 function drawPointAtT(curve) {
451 var x = x_at_t(curve, curveT);
452 var y = y_at_t(curve, curveT);
caryclark55888e42016-07-18 10:01:36 -0700453 drawPoint(x, y, false);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000454 }
455
456 function drawLine(x1, y1, x2, y2) {
457 ctx.beginPath();
458 ctx.moveTo((x1 - srcLeft) * scale,
459 (y1 - srcTop) * scale);
460 ctx.lineTo((x2 - srcLeft) * scale,
461 (y2 - srcTop) * scale);
462 ctx.stroke();
463 }
464
caryclark55888e42016-07-18 10:01:36 -0700465 function drawPoint(px, py, xend) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000466 for (var pts = 0; pts < drawnPts.length; pts += 2) {
467 var x = drawnPts[pts];
468 var y = drawnPts[pts + 1];
469 if (px == x && py == y) {
470 return;
471 }
472 }
473 drawnPts.push(px);
474 drawnPts.push(py);
475 var _px = (px - srcLeft) * scale;
476 var _py = (py - srcTop) * scale;
477 ctx.beginPath();
caryclark55888e42016-07-18 10:01:36 -0700478 if (xend) {
479 ctx.moveTo(_px - 3, _py - 3);
480 ctx.lineTo(_px + 3, _py + 3);
481 ctx.moveTo(_px - 3, _py + 3);
482 ctx.lineTo(_px + 3, _py - 3);
483 } else {
484 ctx.arc(_px, _py, 3, 0, Math.PI * 2, true);
485 ctx.closePath();
486 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000487 ctx.stroke();
488 if (draw_point_xy) {
489 var label = px.toFixed(decimal_places) + ", " + py.toFixed(decimal_places);
490 ctx.font = "normal 10px Arial";
491 ctx.textAlign = "left";
492 ctx.fillStyle = "black";
493 ctx.fillText(label, _px + 5, _py);
494 }
495 }
496
497 function drawPointSolid(px, py) {
caryclark55888e42016-07-18 10:01:36 -0700498 drawPoint(px, py, false);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000499 ctx.fillStyle = "rgba(0,0,0, 0.4)";
500 ctx.fill();
501 }
502
503 function crossPt(origin, pt1, pt2) {
504 return ((pt1[0] - origin[0]) * (pt2[1] - origin[1])
505 - (pt1[1] - origin[1]) * (pt2[0] - origin[0])) > 0 ? 0 : 1;
506 }
507
508 // may not work well for cubics
509 function curveClosestT(curve, x, y) {
510 var closest = -1;
511 var closestDist = Infinity;
512 var l = Infinity, t = Infinity, r = -Infinity, b = -Infinity;
513 for (var i = 0; i < 16; ++i) {
514 var testX = x_at_t(curve, i / 16);
515 l = Math.min(testX, l);
516 r = Math.max(testX, r);
517 var testY = y_at_t(curve, i / 16);
518 t = Math.min(testY, t);
519 b = Math.max(testY, b);
520 var dx = testX - x;
521 var dy = testY - y;
522 var dist = dx * dx + dy * dy;
523 if (closestDist > dist) {
524 closestDist = dist;
525 closest = i;
526 }
527 }
528 var boundsX = r - l;
529 var boundsY = b - t;
530 var boundsDist = boundsX * boundsX + boundsY * boundsY;
531 if (closestDist > boundsDist) {
532 return -1;
533 }
534 console.log("closestDist = " + closestDist + " boundsDist = " + boundsDist
535 + " t = " + closest / 16);
536 return closest / 16;
537 }
538
caryclark1049f122015-04-20 08:31:59 -0700539 var kMaxConicToQuadPOW2 = 5;
540
541 function computeQuadPOW2(curve, tol) {
542 var a = curve[6] - 1;
543 var k = a / (4 * (2 + a));
544 var x = k * (curve[0] - 2 * curve[2] + curve[4]);
545 var y = k * (curve[1] - 2 * curve[3] + curve[5]);
546
547 var error = Math.sqrt(x * x + y * y);
548 var pow2;
549 for (pow2 = 0; pow2 < kMaxConicToQuadPOW2; ++pow2) {
550 if (error <= tol) {
551 break;
552 }
553 error *= 0.25;
554 }
555 return pow2;
556 }
557
558 function subdivide_w_value(w) {
559 return Math.sqrt(0.5 + w * 0.5);
560 }
561
562 function chop(curve, part1, part2) {
563 var w = curve[6];
564 var scale = 1 / (1 + w);
565 part1[0] = curve[0];
566 part1[1] = curve[1];
567 part1[2] = (curve[0] + curve[2] * w) * scale;
568 part1[3] = (curve[1] + curve[3] * w) * scale;
569 part1[4] = part2[0] = (curve[0] + (curve[2] * w) * 2 + curve[4]) * scale * 0.5;
570 part1[5] = part2[1] = (curve[1] + (curve[3] * w) * 2 + curve[5]) * scale * 0.5;
571 part2[2] = (curve[2] * w + curve[4]) * scale;
572 part2[3] = (curve[3] * w + curve[5]) * scale;
573 part2[4] = curve[4];
574 part2[5] = curve[5];
575 part1[6] = part2[6] = subdivide_w_value(w);
576 }
577
578 function subdivide(curve, level, pts) {
579 if (0 == level) {
580 pts.push(curve[2]);
581 pts.push(curve[3]);
582 pts.push(curve[4]);
583 pts.push(curve[5]);
584 } else {
585 var part1 = [], part2 = [];
586 chop(curve, part1, part2);
587 --level;
588 subdivide(part1, level, pts);
589 subdivide(part2, level, pts);
590 }
591 }
592
593 function chopIntoQuadsPOW2(curve, pow2, pts) {
594 subdivide(curve, pow2, pts);
595 return 1 << pow2;
596 }
597
598 function drawConic(curve, srcLeft, srcTop, scale) {
599 var tol = 1 / scale;
600 var pow2 = computeQuadPOW2(curve, tol);
601 var pts = [];
602 chopIntoQuadsPOW2(curve, pow2, pts);
603 for (var i = 0; i < pts.length; i += 4) {
604 ctx.quadraticCurveTo(
605 (pts[i + 0] - srcLeft) * scale, (pts[i + 1] - srcTop) * scale,
606 (pts[i + 2] - srcLeft) * scale, (pts[i + 3] - srcTop) * scale);
607 }
608 }
609
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000610 function draw(test, title) {
611 ctx.font = "normal 50px Arial";
612 ctx.textAlign = "left";
613 ctx.fillStyle = "rgba(0,0,0, 0.1)";
614 ctx.fillText(title, 50, 50);
615 ctx.font = "normal 10px Arial";
616 // ctx.lineWidth = "1.001"; "0.999";
617 var hullStarts = [];
618 var hullEnds = [];
619 var midSpokes = [];
620 var midDist = [];
621 var origin = [];
622 var shortSpokes = [];
623 var shortDist = [];
624 var sweeps = [];
625 drawnPts = [];
626 for (var curves in test) {
627 var curve = test[curves];
628 origin.push(curve[0]);
629 origin.push(curve[1]);
630 var startPt = [];
631 startPt.push(curve[2]);
632 startPt.push(curve[3]);
633 hullStarts.push(startPt);
634 var endPt = [];
635 if (curve.length == 4) {
636 endPt.push(curve[2]);
637 endPt.push(curve[3]);
caryclark1049f122015-04-20 08:31:59 -0700638 } else if (curve.length == 6 || curve.length == 7) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000639 endPt.push(curve[4]);
640 endPt.push(curve[5]);
641 } else if (curve.length == 8) {
642 endPt.push(curve[6]);
643 endPt.push(curve[7]);
644 }
645 hullEnds.push(endPt);
646 var sweep = crossPt(origin, startPt, endPt);
647 sweeps.push(sweep);
648 var midPt = [];
649 midPt.push(x_at_t(curve, 0.5));
650 midPt.push(y_at_t(curve, 0.5));
651 midSpokes.push(midPt);
652 var shortPt = [];
653 shortPt.push(x_at_t(curve, 0.25));
654 shortPt.push(y_at_t(curve, 0.25));
655 shortSpokes.push(shortPt);
656 var dx = midPt[0] - origin[0];
657 var dy = midPt[1] - origin[1];
658 var dist = Math.sqrt(dx * dx + dy * dy);
659 midDist.push(dist);
660 dx = shortPt[0] - origin[0];
661 dy = shortPt[1] - origin[1];
662 dist = Math.sqrt(dx * dx + dy * dy);
663 shortDist.push(dist);
664 }
665 var intersect = [];
666 var useIntersect = false;
667 var maxWidth = Math.max(xmax - xmin, ymax - ymin);
668 for (var curves in test) {
669 var curve = test[curves];
caryclark1049f122015-04-20 08:31:59 -0700670 if (curve.length >= 6 && curve.length <= 8) {
commit-bot@chromium.org8cb1daa2014-04-25 12:59:11 +0000671 var opp = curves == 0 || curves == 1 ? 0 : 1;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000672 var sects = ray_curve_intersect(origin, hullEnds[opp], curve);
673 intersect.push(sects);
674 if (sects.length > 1) {
675 var intersection = sects[0];
676 if (intersection == 0) {
677 intersection = sects[1];
678 }
679 var ix = x_at_t(curve, intersection) - origin[0];
680 var iy = y_at_t(curve, intersection) - origin[1];
681 var ex = hullEnds[opp][0] - origin[0];
682 var ey = hullEnds[opp][1] - origin[1];
683 if (ix * ex >= 0 && iy * ey >= 0) {
684 var iDist = Math.sqrt(ix * ix + iy * iy);
685 var eDist = Math.sqrt(ex * ex + ey * ey);
686 var delta = Math.abs(iDist - eDist) / maxWidth;
caryclark1049f122015-04-20 08:31:59 -0700687 if (delta > (curve.length != 8 ? 1e-5 : 1e-4)) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000688 useIntersect ^= true;
689 }
690 }
691 }
692 }
693 }
commit-bot@chromium.org8cb1daa2014-04-25 12:59:11 +0000694 var midLeft = curves != 0 ? crossPt(origin, midSpokes[0], midSpokes[1]) : 0;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000695 var firstInside;
696 if (useIntersect) {
697 var sect1 = intersect[0].length > 1;
698 var sIndex = sect1 ? 0 : 1;
699 var sects = intersect[sIndex];
700 var intersection = sects[0];
701 if (intersection == 0) {
702 intersection = sects[1];
703 }
704 var curve = test[sIndex];
705 var ix = x_at_t(curve, intersection) - origin[0];
706 var iy = y_at_t(curve, intersection) - origin[1];
707 var opp = sect1 ? 1 : 0;
708 var ex = hullEnds[opp][0] - origin[0];
709 var ey = hullEnds[opp][1] - origin[1];
710 var iDist = ix * ix + iy * iy;
711 var eDist = ex * ex + ey * ey;
712 firstInside = (iDist > eDist) ^ (sIndex == 0) ^ sweeps[0];
713// console.log("iDist=" + iDist + " eDist=" + eDist + " sIndex=" + sIndex
714 // + " sweeps[0]=" + sweeps[0]);
715 } else {
716 // console.log("midLeft=" + midLeft);
717 firstInside = midLeft != 0;
718 }
719 var shorter = midDist[1] < midDist[0];
720 var shortLeft = shorter ? crossPt(origin, shortSpokes[0], midSpokes[1])
721 : crossPt(origin, midSpokes[0], shortSpokes[1]);
722 var startCross = crossPt(origin, hullStarts[0], hullStarts[1]);
723 var disallowShort = midLeft == startCross && midLeft == sweeps[0]
724 && midLeft == sweeps[1];
725
726 // console.log("midLeft=" + midLeft + " startCross=" + startCross);
727 var intersectIndex = 0;
728 for (var curves in test) {
caryclark1049f122015-04-20 08:31:59 -0700729 var curve = test[draw_id != 2 ? curves : test.length - curves - 1];
730 if (curve.length != 4 && curve.length != 6 && curve.length != 7 && curve.length != 8) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000731 continue;
732 }
733 ctx.lineWidth = 1;
734 if (draw_tangents != 0) {
caryclarkfa6d6562014-07-29 12:13:28 -0700735 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000736 ctx.strokeStyle = "rgba(255,0,0, 0.3)";
737 } else {
738 ctx.strokeStyle = "rgba(0,0,255, 0.3)";
739 }
740 drawLine(curve[0], curve[1], curve[2], curve[3]);
741 if (draw_tangents != 2) {
742 if (curve.length > 4) drawLine(curve[2], curve[3], curve[4], curve[5]);
caryclark1049f122015-04-20 08:31:59 -0700743 if (curve.length == 8) drawLine(curve[4], curve[5], curve[6], curve[7]);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000744 }
745 if (draw_tangents != 1) {
caryclark1049f122015-04-20 08:31:59 -0700746 if (curve.length == 6 || curve.length == 7) {
747 drawLine(curve[0], curve[1], curve[4], curve[5]);
748 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000749 if (curve.length == 8) drawLine(curve[0], curve[1], curve[6], curve[7]);
750 }
751 }
752 ctx.beginPath();
753 ctx.moveTo((curve[0] - srcLeft) * scale, (curve[1] - srcTop) * scale);
754 if (curve.length == 4) {
755 ctx.lineTo((curve[2] - srcLeft) * scale, (curve[3] - srcTop) * scale);
756 } else if (curve.length == 6) {
757 ctx.quadraticCurveTo(
758 (curve[2] - srcLeft) * scale, (curve[3] - srcTop) * scale,
759 (curve[4] - srcLeft) * scale, (curve[5] - srcTop) * scale);
caryclark1049f122015-04-20 08:31:59 -0700760 } else if (curve.length == 7) {
761 drawConic(curve, srcLeft, srcTop, scale);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000762 } else {
763 ctx.bezierCurveTo(
764 (curve[2] - srcLeft) * scale, (curve[3] - srcTop) * scale,
765 (curve[4] - srcLeft) * scale, (curve[5] - srcTop) * scale,
766 (curve[6] - srcLeft) * scale, (curve[7] - srcTop) * scale);
767 }
caryclarkfa6d6562014-07-29 12:13:28 -0700768 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000769 ctx.strokeStyle = "rgba(255,0,0, 1)";
770 } else {
771 ctx.strokeStyle = "rgba(0,0,255, 1)";
772 }
773 ctx.stroke();
caryclark54359292015-03-26 07:52:43 -0700774 if (draw_endpoints > 0) {
caryclark55888e42016-07-18 10:01:36 -0700775 drawPoint(curve[0], curve[1], false);
caryclark54359292015-03-26 07:52:43 -0700776 if (draw_endpoints > 1 || curve.length == 4) {
caryclark55888e42016-07-18 10:01:36 -0700777 drawPoint(curve[2], curve[3], curve.length == 4 && draw_endpoints == 3);
caryclark54359292015-03-26 07:52:43 -0700778 }
caryclark1049f122015-04-20 08:31:59 -0700779 if (curve.length == 6 || curve.length == 7 ||
780 (draw_endpoints > 1 && curve.length == 8)) {
caryclark55888e42016-07-18 10:01:36 -0700781 drawPoint(curve[4], curve[5], (curve.length == 6 || curve.length == 7) && draw_endpoints == 3);
caryclark54359292015-03-26 07:52:43 -0700782 }
caryclark55888e42016-07-18 10:01:36 -0700783 if (curve.length == 8) {
784 drawPoint(curve[6], curve[7], curve.length == 8 && draw_endpoints == 3);
785 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000786 }
787 if (draw_midpoint != 0) {
788 if ((curves == 0) == (midLeft == 0)) {
789 ctx.strokeStyle = "rgba(0,180,127, 0.6)";
790 } else {
791 ctx.strokeStyle = "rgba(127,0,127, 0.6)";
792 }
793 var midX = x_at_t(curve, 0.5);
794 var midY = y_at_t(curve, 0.5);
795 drawPointSolid(midX, midY);
796 if (draw_midpoint > 1) {
797 drawLine(curve[0], curve[1], midX, midY);
798 }
799 }
800 if (draw_quarterpoint != 0) {
801 if ((curves == 0) == (shortLeft == 0)) {
802 ctx.strokeStyle = "rgba(0,191,63, 0.6)";
803 } else {
804 ctx.strokeStyle = "rgba(63,0,191, 0.6)";
805 }
806 var midT = (curves == 0) == shorter ? 0.25 : 0.5;
807 var midX = x_at_t(curve, midT);
808 var midY = y_at_t(curve, midT);
809 drawPointSolid(midX, midY);
810 if (draw_quarterpoint > 1) {
811 drawLine(curve[0], curve[1], midX, midY);
812 }
813 }
814 if (draw_sortpoint != 0) {
815 if ((curves == 0) == ((disallowShort == -1 ? midLeft : shortLeft) == 0)) {
816 ctx.strokeStyle = "rgba(0,155,37, 0.6)";
817 } else {
818 ctx.strokeStyle = "rgba(37,0,155, 0.6)";
819 }
820 var midT = (curves == 0) == shorter && disallowShort != curves ? 0.25 : 0.5;
821 console.log("curves=" + curves + " disallowShort=" + disallowShort
822 + " midLeft=" + midLeft + " shortLeft=" + shortLeft
823 + " shorter=" + shorter + " midT=" + midT);
824 var midX = x_at_t(curve, midT);
825 var midY = y_at_t(curve, midT);
826 drawPointSolid(midX, midY);
827 if (draw_sortpoint > 1) {
828 drawLine(curve[0], curve[1], midX, midY);
829 }
830 }
831 if (draw_ray_intersect != 0) {
832 ctx.strokeStyle = "rgba(75,45,199, 0.6)";
caryclark1049f122015-04-20 08:31:59 -0700833 if (curve.length >= 6 && curve.length <= 8) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000834 var intersections = intersect[intersectIndex];
835 for (var i in intersections) {
836 var intersection = intersections[i];
837 var x = x_at_t(curve, intersection);
838 var y = y_at_t(curve, intersection);
839 drawPointSolid(x, y);
840 if (draw_ray_intersect > 1) {
841 drawLine(curve[0], curve[1], x, y);
842 }
843 }
844 }
845 ++intersectIndex;
846 }
847 if (draw_order) {
848 var px = x_at_t(curve, 0.75);
849 var py = y_at_t(curve, 0.75);
850 var _px = (px - srcLeft) * scale;
851 var _py = (py - srcTop) * scale;
852 ctx.beginPath();
853 ctx.arc(_px, _py, 15, 0, Math.PI * 2, true);
854 ctx.closePath();
855 ctx.fillStyle = "white";
856 ctx.fill();
caryclarkfa6d6562014-07-29 12:13:28 -0700857 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000858 ctx.strokeStyle = "rgba(255,0,0, 1)";
859 ctx.fillStyle = "rgba(255,0,0, 1)";
860 } else {
861 ctx.strokeStyle = "rgba(0,0,255, 1)";
862 ctx.fillStyle = "rgba(0,0,255, 1)";
863 }
864 ctx.stroke();
865 ctx.font = "normal 16px Arial";
866 ctx.textAlign = "center";
867 ctx.fillText(parseInt(curves) + 1, _px, _py + 5);
868 }
869 if (draw_closest_t) {
870 var t = curveClosestT(curve, mouseX, mouseY);
871 if (t >= 0) {
872 var x = x_at_t(curve, t);
873 var y = y_at_t(curve, t);
874 drawPointSolid(x, y);
875 }
876 }
877 if (!approximately_zero(scale - initScale)) {
878 ctx.font = "normal 20px Arial";
879 ctx.fillStyle = "rgba(0,0,0, 0.3)";
880 ctx.textAlign = "right";
881 ctx.fillText(scale.toFixed(decimal_places) + 'x',
882 screenWidth - 10, screenHeight - 5);
883 }
884 if (draw_t) {
885 drawPointAtT(curve);
886 }
caryclark1049f122015-04-20 08:31:59 -0700887 if (draw_id != 0) {
caryclark54359292015-03-26 07:52:43 -0700888 var id = -1;
889 for (var i = 0; i < ids.length; i += 2) {
890 if (ids[i + 1] == curve) {
891 id = ids[i];
892 break;
893 }
894 }
895 if (id >= 0) {
896 var px = x_at_t(curve, 0.5);
897 var py = y_at_t(curve, 0.5);
898 var _px = (px - srcLeft) * scale;
899 var _py = (py - srcTop) * scale;
900 ctx.beginPath();
901 ctx.arc(_px, _py, 15, 0, Math.PI * 2, true);
902 ctx.closePath();
903 ctx.fillStyle = "white";
904 ctx.fill();
905 ctx.strokeStyle = "rgba(255,0,0, 1)";
906 ctx.fillStyle = "rgba(255,0,0, 1)";
907 ctx.stroke();
908 ctx.font = "normal 16px Arial";
909 ctx.textAlign = "center";
910 ctx.fillText(id, _px, _py + 5);
911 }
912 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000913 }
914 if (draw_t) {
915 drawCurveTControl();
916 }
caryclark1049f122015-04-20 08:31:59 -0700917 if (draw_w) {
918 drawCurveWControl();
919 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000920 }
921
922 function drawCurveTControl() {
923 ctx.lineWidth = 2;
924 ctx.strokeStyle = "rgba(0,0,0, 0.3)";
925 ctx.beginPath();
926 ctx.rect(screenWidth - 80, 40, 28, screenHeight - 80);
927 ctx.stroke();
928 var ty = 40 + curveT * (screenHeight - 80);
929 ctx.beginPath();
930 ctx.moveTo(screenWidth - 80, ty);
931 ctx.lineTo(screenWidth - 85, ty - 5);
932 ctx.lineTo(screenWidth - 85, ty + 5);
933 ctx.lineTo(screenWidth - 80, ty);
934 ctx.fillStyle = "rgba(0,0,0, 0.6)";
935 ctx.fill();
936 var num = curveT.toFixed(decimal_places);
937 ctx.font = "normal 10px Arial";
938 ctx.textAlign = "left";
939 ctx.fillText(num, screenWidth - 78, ty);
940 }
941
caryclark1049f122015-04-20 08:31:59 -0700942 function drawCurveWControl() {
943 var w = -1;
944 var choice = 0;
945 for (var curves in tests[testIndex]) {
946 var curve = tests[testIndex][curves];
947 if (curve.length != 7) {
948 continue;
949 }
950 if (choice == curveW) {
951 w = curve[6];
952 break;
953 }
954 ++choice;
955 }
956 if (w < 0) {
957 return;
958 }
959 ctx.lineWidth = 2;
960 ctx.strokeStyle = "rgba(0,0,0, 0.3)";
961 ctx.beginPath();
962 ctx.rect(screenWidth - 40, 40, 28, screenHeight - 80);
963 ctx.stroke();
964 var ty = 40 + w * (screenHeight - 80);
965 ctx.beginPath();
966 ctx.moveTo(screenWidth - 40, ty);
967 ctx.lineTo(screenWidth - 45, ty - 5);
968 ctx.lineTo(screenWidth - 45, ty + 5);
969 ctx.lineTo(screenWidth - 40, ty);
970 ctx.fillStyle = "rgba(0,0,0, 0.6)";
971 ctx.fill();
972 var num = w.toFixed(decimal_places);
973 ctx.font = "normal 10px Arial";
974 ctx.textAlign = "left";
975 ctx.fillText(num, screenWidth - 38, ty);
976 }
977
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000978 function ptInTControl() {
979 var e = window.event;
980 var tgt = e.target || e.srcElement;
981 var left = tgt.offsetLeft;
982 var top = tgt.offsetTop;
983 var x = (e.clientX - left);
984 var y = (e.clientY - top);
985 if (x < screenWidth - 80 || x > screenWidth - 50) {
986 return false;
987 }
988 if (y < 40 || y > screenHeight - 80) {
989 return false;
990 }
991 curveT = (y - 40) / (screenHeight - 120);
992 if (curveT < 0 || curveT > 1) {
993 throw "stop execution";
994 }
995 return true;
996 }
997
caryclark1049f122015-04-20 08:31:59 -0700998 function ptInWControl() {
999 var e = window.event;
1000 var tgt = e.target || e.srcElement;
1001 var left = tgt.offsetLeft;
1002 var top = tgt.offsetTop;
1003 var x = (e.clientX - left);
1004 var y = (e.clientY - top);
1005 if (x < screenWidth - 40 || x > screenWidth - 10) {
1006 return false;
1007 }
1008 if (y < 40 || y > screenHeight - 80) {
1009 return false;
1010 }
1011 var w = (y - 40) / (screenHeight - 120);
1012 if (w < 0 || w > 1) {
1013 throw "stop execution";
1014 }
1015 var choice = 0;
1016 for (var curves in tests[testIndex]) {
1017 var curve = tests[testIndex][curves];
1018 if (curve.length != 7) {
1019 continue;
1020 }
1021 if (choice == curveW) {
1022 curve[6] = w;
1023 break;
1024 }
1025 ++choice;
1026 }
1027 return true;
1028 }
1029
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001030 function drawTop() {
1031 init(tests[testIndex]);
1032 redraw();
1033 }
1034
1035 function redraw() {
caryclark54359292015-03-26 07:52:43 -07001036 if (focus_on_selection > 0) {
1037 var focusXmin = focusYmin = Infinity;
1038 var focusXmax = focusYmax = -Infinity;
1039 var choice = 0;
1040 for (var curves in tests[testIndex]) {
1041 if (++choice != focus_on_selection) {
1042 continue;
1043 }
1044 var curve = tests[testIndex][curves];
caryclark1049f122015-04-20 08:31:59 -07001045 var last = curve.length - (curve.length % 2 == 1 ? 1 : 0);
caryclark54359292015-03-26 07:52:43 -07001046 for (var idx = 0; idx < last; idx += 2) {
1047 focusXmin = Math.min(focusXmin, curve[idx]);
1048 focusXmax = Math.max(focusXmax, curve[idx]);
1049 focusYmin = Math.min(focusYmin, curve[idx + 1]);
1050 focusYmax = Math.max(focusYmax, curve[idx + 1]);
1051 }
1052 }
1053 focusXmin -= Math.min(1, Math.max(focusXmax - focusXmin, focusYmax - focusYmin));
1054 if (focusXmin < focusXmax && focusYmin < focusYmax) {
1055 setScale(focusXmin, focusXmax, focusYmin, focusYmax);
1056 }
1057 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001058 ctx.beginPath();
1059 ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);
1060 ctx.fillStyle = "white";
1061 ctx.fill();
1062 draw(tests[testIndex], testTitles[testIndex]);
1063 }
1064
1065 function doKeyPress(evt) {
1066 var char = String.fromCharCode(evt.charCode);
caryclark54359292015-03-26 07:52:43 -07001067 var focusWasOn = false;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001068 switch (char) {
1069 case '0':
1070 case '1':
1071 case '2':
1072 case '3':
1073 case '4':
1074 case '5':
1075 case '6':
1076 case '7':
1077 case '8':
1078 case '9':
1079 decimal_places = char - '0';
1080 redraw();
1081 break;
1082 case '-':
caryclark54359292015-03-26 07:52:43 -07001083 focusWasOn = focus_on_selection;
1084 if (focusWasOn) {
1085 focus_on_selection = false;
1086 scale /= 1.2;
1087 } else {
1088 scale /= 2;
1089 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001090 calcLeftTop();
1091 redraw();
caryclark54359292015-03-26 07:52:43 -07001092 focus_on_selection = focusWasOn;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001093 break;
1094 case '=':
1095 case '+':
caryclark54359292015-03-26 07:52:43 -07001096 focusWasOn = focus_on_selection;
1097 if (focusWasOn) {
1098 focus_on_selection = false;
1099 scale *= 1.2;
1100 } else {
1101 scale *= 2;
1102 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001103 calcLeftTop();
1104 redraw();
caryclark54359292015-03-26 07:52:43 -07001105 focus_on_selection = focusWasOn;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001106 break;
caryclarkfa6d6562014-07-29 12:13:28 -07001107 case 'b':
1108 draw_cubic_red ^= true;
1109 redraw();
1110 break;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001111 case 'c':
1112 drawTop();
1113 break;
1114 case 'd':
1115 var test = tests[testIndex];
1116 var testClone = [];
1117 for (var curves in test) {
1118 var c = test[curves];
1119 var cClone = [];
1120 for (var index = 0; index < c.length; ++index) {
1121 cClone.push(c[index]);
1122 }
1123 testClone.push(cClone);
1124 }
1125 tests.push(testClone);
1126 testTitles.push(testTitles[testIndex] + " copy");
1127 testIndex = tests.length - 1;
1128 redraw();
1129 break;
1130 case 'e':
caryclark55888e42016-07-18 10:01:36 -07001131 draw_endpoints = (draw_endpoints + 1) % 4;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001132 redraw();
1133 break;
1134 case 'f':
1135 draw_derivative ^= true;
1136 redraw();
1137 break;
1138 case 'i':
1139 draw_ray_intersect = (draw_ray_intersect + 1) % 3;
1140 redraw();
1141 break;
1142 case 'l':
1143 var test = tests[testIndex];
1144 console.log("<div id=\"" + testTitles[testIndex] + "\" >");
1145 for (var curves in test) {
1146 var c = test[curves];
1147 var s = "{{";
1148 for (var i = 0; i < c.length; i += 2) {
1149 s += "{";
1150 s += c[i] + "," + c[i + 1];
1151 s += "}";
1152 if (i + 2 < c.length) {
1153 s += ", ";
1154 }
1155 }
1156 console.log(s + "}},");
1157 }
1158 console.log("</div>");
1159 break;
1160 case 'm':
1161 draw_midpoint = (draw_midpoint + 1) % 3;
1162 redraw();
1163 break;
1164 case 'N':
1165 testIndex += 9;
1166 case 'n':
1167 testIndex = (testIndex + 1) % tests.length;
1168 drawTop();
1169 break;
1170 case 'o':
1171 draw_order ^= true;
1172 redraw();
1173 break;
1174 case 'P':
1175 testIndex -= 9;
1176 case 'p':
1177 if (--testIndex < 0)
1178 testIndex = tests.length - 1;
1179 drawTop();
1180 break;
1181 case 'q':
1182 draw_quarterpoint = (draw_quarterpoint + 1) % 3;
1183 redraw();
1184 break;
1185 case 'r':
1186 for (var i = 0; i < testDivs.length; ++i) {
1187 var title = testDivs[i].id.toString();
1188 if (title == testTitles[testIndex]) {
1189 var str = testDivs[i].firstChild.data;
1190 parse(str, title);
1191 var original = tests.pop();
1192 testTitles.pop();
1193 tests[testIndex] = original;
1194 break;
1195 }
1196 }
1197 redraw();
1198 break;
1199 case 's':
1200 draw_sortpoint = (draw_sortpoint + 1) % 3;
1201 redraw();
1202 break;
1203 case 't':
1204 draw_t ^= true;
1205 redraw();
1206 break;
1207 case 'u':
1208 draw_closest_t ^= true;
1209 redraw();
1210 break;
1211 case 'v':
1212 draw_tangents = (draw_tangents + 1) % 4;
1213 redraw();
1214 break;
caryclark1049f122015-04-20 08:31:59 -07001215 case 'w':
1216 ++curveW;
1217 var choice = 0;
1218 draw_w = false;
1219 for (var curves in tests[testIndex]) {
1220 var curve = tests[testIndex][curves];
1221 if (curve.length != 7) {
1222 continue;
1223 }
1224 if (choice == curveW) {
1225 draw_w = true;
1226 break;
1227 }
1228 ++choice;
1229 }
1230 if (!draw_w) {
1231 curveW = -1;
1232 }
1233 redraw();
1234 break;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001235 case 'x':
1236 draw_point_xy ^= true;
1237 redraw();
1238 break;
1239 case 'y':
1240 draw_mouse_xy ^= true;
1241 redraw();
1242 break;
1243 case '\\':
1244 retina_scale ^= true;
1245 drawTop();
1246 break;
caryclark54359292015-03-26 07:52:43 -07001247 case '`':
1248 ++focus_on_selection;
1249 if (focus_on_selection >= tests[testIndex].length) {
1250 focus_on_selection = 0;
1251 }
1252 setScale(xmin, xmax, ymin, ymax);
1253 redraw();
1254 break;
1255 case '.':
caryclark1049f122015-04-20 08:31:59 -07001256 draw_id = (draw_id + 1) % 3;
caryclark54359292015-03-26 07:52:43 -07001257 redraw();
1258 break;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001259 }
1260 }
1261
1262 function doKeyDown(evt) {
1263 var char = evt.keyCode;
1264 var preventDefault = false;
1265 switch (char) {
1266 case 37: // left arrow
1267 if (evt.shiftKey) {
1268 testIndex -= 9;
1269 }
1270 if (--testIndex < 0)
1271 testIndex = tests.length - 1;
caryclark54359292015-03-26 07:52:43 -07001272 if (evt.ctrlKey) {
1273 redraw();
1274 } else {
1275 drawTop();
1276 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001277 preventDefault = true;
1278 break;
1279 case 39: // right arrow
1280 if (evt.shiftKey) {
1281 testIndex += 9;
1282 }
1283 if (++testIndex >= tests.length)
1284 testIndex = 0;
caryclark54359292015-03-26 07:52:43 -07001285 if (evt.ctrlKey) {
1286 redraw();
1287 } else {
1288 drawTop();
1289 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001290 preventDefault = true;
1291 break;
1292 }
1293 if (preventDefault) {
1294 evt.preventDefault();
1295 return false;
1296 }
1297 return true;
1298 }
1299
1300 function calcXY() {
1301 var e = window.event;
1302 var tgt = e.target || e.srcElement;
1303 var left = tgt.offsetLeft;
1304 var top = tgt.offsetTop;
1305 mouseX = (e.clientX - left) / scale + srcLeft;
1306 mouseY = (e.clientY - top) / scale + srcTop;
1307 }
1308
1309 function calcLeftTop() {
1310 srcLeft = mouseX - screenWidth / 2 / scale;
1311 srcTop = mouseY - screenHeight / 2 / scale;
1312 }
1313
1314 function handleMouseClick() {
caryclark1049f122015-04-20 08:31:59 -07001315 if ((!draw_t || !ptInTControl()) && (!draw_w || !ptInWControl())) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001316 calcXY();
1317 } else {
1318 redraw();
1319 }
1320 }
1321
1322 function initDown() {
1323 var test = tests[testIndex];
1324 var bestDistance = 1000000;
1325 activePt = -1;
1326 for (var curves in test) {
1327 var testCurve = test[curves];
caryclark1049f122015-04-20 08:31:59 -07001328 if (testCurve.length != 4 && (testCurve.length < 6 || testCurve.length > 8)) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001329 continue;
1330 }
caryclark1049f122015-04-20 08:31:59 -07001331 var testMax = testCurve.length == 7 ? 6 : testCurve.length;
1332 for (var i = 0; i < testMax; i += 2) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001333 var testX = testCurve[i];
1334 var testY = testCurve[i + 1];
1335 var dx = testX - mouseX;
1336 var dy = testY - mouseY;
1337 var dist = dx * dx + dy * dy;
1338 if (dist > bestDistance) {
1339 continue;
1340 }
1341 activeCurve = testCurve;
1342 activePt = i;
1343 bestDistance = dist;
1344 }
1345 }
1346 if (activePt >= 0) {
1347 lastX = mouseX;
1348 lastY = mouseY;
1349 }
1350 }
1351
1352 function handleMouseOver() {
1353 calcXY();
1354 if (draw_mouse_xy) {
1355 var num = mouseX.toFixed(decimal_places) + ", " + mouseY.toFixed(decimal_places);
1356 ctx.beginPath();
1357 ctx.rect(300, 100, num.length * 6, 10);
1358 ctx.fillStyle = "white";
1359 ctx.fill();
1360 ctx.font = "normal 10px Arial";
1361 ctx.fillStyle = "black";
1362 ctx.textAlign = "left";
1363 ctx.fillText(num, 300, 108);
1364 }
1365 if (!mouseDown) {
1366 activePt = -1;
1367 return;
1368 }
1369 if (activePt < 0) {
1370 initDown();
1371 return;
1372 }
1373 var deltaX = mouseX - lastX;
1374 var deltaY = mouseY - lastY;
1375 lastX = mouseX;
1376 lastY = mouseY;
1377 if (activePt == 0) {
1378 var test = tests[testIndex];
1379 for (var curves in test) {
1380 var testCurve = test[curves];
1381 testCurve[0] += deltaX;
1382 testCurve[1] += deltaY;
1383 }
1384 } else {
1385 activeCurve[activePt] += deltaX;
1386 activeCurve[activePt + 1] += deltaY;
1387 }
1388 redraw();
1389 }
1390
1391 function start() {
1392 for (var i = 0; i < testDivs.length; ++i) {
1393 var title = testDivs[i].id.toString();
1394 var str = testDivs[i].firstChild.data;
1395 parse(str, title);
1396 }
1397 drawTop();
1398 window.addEventListener('keypress', doKeyPress, true);
1399 window.addEventListener('keydown', doKeyDown, true);
1400 window.onresize = function () {
1401 drawTop();
1402 }
1403 }
1404
1405</script>
1406</head>
1407
1408<body onLoad="start();">
1409
1410<canvas id="canvas" width="750" height="500"
1411 onmousedown="mouseDown = true"
1412 onmouseup="mouseDown = false"
1413 onmousemove="handleMouseOver()"
1414 onclick="handleMouseClick()"
1415 ></canvas >
1416</body>
1417</html>