blob: 967ac316eb407999b4ba3b29866c4f8ea55b26c5 [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 Clark59d5a0e2017-01-23 14:38:52 +00009<div id="cubics">
10{{{403.28299,497.196991}, {403.424011,497.243988}, {391.110992,495.556}, {391.110992,495.556}}}
11{{{398.375,501.976013}, {403.28299,497.196991}}}
12{{{403.42401123046875, 497.243988037109375}, {378.7979736328125, 493.868011474609375}}}, id=0
13{{{415.5605946972181641, 498.8838844379989155}, {378.8318672594865006, 493.8897667505245295}}}, id=1
14{{{403.2954048004600622, 497.117149457477467}, {403.2729768294798873, 497.2781265045034615}}}, id=2
15{{{403.42401123046875, 497.243988037109375}, {378.7979736328125, 493.868011474609375}}}, id=3
16
17</div>
18
19<div id="c1">
20{{{403.28299f, 497.196991f}, {403.284241f, 497.197388f}, {403.28418f, 497.197632f}}}
21{{{403.28418f, 497.197632f}, {403.280487f, 497.224304f}, {391.110992f, 495.556f}, {391.110992f, 495.556f}}}
22
Cary Clark7eb01e02016-12-08 14:36:32 -050023</div>
caryclark34efb702016-10-24 08:19:06 -070024 </div>
25
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000026<script type="text/javascript">
27
caryclark34efb702016-10-24 08:19:06 -070028 var testDivs = [
Cary Clark59d5a0e2017-01-23 14:38:52 +000029 c1,
30 cubics,
caryclarkb36a3cd2016-10-18 07:59:44 -070031 ];
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000032
caryclark54359292015-03-26 07:52:43 -070033 var decimal_places = 3;
34
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000035 var tests = [];
36 var testTitles = [];
37 var testIndex = 0;
38 var ctx;
caryclark54359292015-03-26 07:52:43 -070039
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000040 var subscale = 1;
41 var xmin, xmax, ymin, ymax;
42 var scale;
43 var initScale;
44 var mouseX, mouseY;
45 var mouseDown = false;
46 var srcLeft, srcTop;
47 var screenWidth, screenHeight;
48 var drawnPts;
49 var curveT = 0;
caryclark1049f122015-04-20 08:31:59 -070050 var curveW = -1;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000051
52 var lastX, lastY;
53 var activeCurve = [];
54 var activePt;
caryclark54359292015-03-26 07:52:43 -070055 var ids = [];
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000056
caryclark54359292015-03-26 07:52:43 -070057 var focus_on_selection = 0;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000058 var draw_t = false;
caryclark1049f122015-04-20 08:31:59 -070059 var draw_w = false;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000060 var draw_closest_t = false;
caryclarkfa6d6562014-07-29 12:13:28 -070061 var draw_cubic_red = false;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000062 var draw_derivative = false;
caryclark54359292015-03-26 07:52:43 -070063 var draw_endpoints = 2;
caryclark1049f122015-04-20 08:31:59 -070064 var draw_id = 0;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000065 var draw_midpoint = 0;
66 var draw_mouse_xy = false;
67 var draw_order = false;
68 var draw_point_xy = false;
69 var draw_ray_intersect = false;
70 var draw_quarterpoint = 0;
71 var draw_tangents = 1;
72 var draw_sortpoint = 0;
73 var retina_scale = !!window.devicePixelRatio;
74
75 function parse(test, title) {
76 var curveStrs = test.split("{{");
77 var pattern = /-?\d+\.*\d*e?-?\d*/g;
78 var curves = [];
79 for (var c in curveStrs) {
80 var curveStr = curveStrs[c];
caryclark54359292015-03-26 07:52:43 -070081 var idPart = curveStr.split("id=");
82 var id = -1;
83 if (idPart.length == 2) {
84 id = parseInt(idPart[1]);
85 curveStr = idPart[0];
86 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000087 var points = curveStr.match(pattern);
88 var pts = [];
89 for (var wd in points) {
90 var num = parseFloat(points[wd]);
91 if (isNaN(num)) continue;
92 pts.push(num);
93 }
caryclark54359292015-03-26 07:52:43 -070094 if (pts.length > 2) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000095 curves.push(pts);
caryclark54359292015-03-26 07:52:43 -070096 }
97 if (id >= 0) {
98 ids.push(id);
99 ids.push(pts);
100 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000101 }
102 if (curves.length >= 1) {
103 tests.push(curves);
104 testTitles.push(title);
105 }
106 }
107
108 function init(test) {
109 var canvas = document.getElementById('canvas');
110 if (!canvas.getContext) return;
111 ctx = canvas.getContext('2d');
112 var resScale = retina_scale && window.devicePixelRatio ? window.devicePixelRatio : 1;
113 var unscaledWidth = window.innerWidth - 20;
114 var unscaledHeight = window.innerHeight - 20;
115 screenWidth = unscaledWidth;
116 screenHeight = unscaledHeight;
117 canvas.width = unscaledWidth * resScale;
118 canvas.height = unscaledHeight * resScale;
119 canvas.style.width = unscaledWidth + 'px';
120 canvas.style.height = unscaledHeight + 'px';
121 if (resScale != 1) {
122 ctx.scale(resScale, resScale);
123 }
124 xmin = Infinity;
125 xmax = -Infinity;
126 ymin = Infinity;
127 ymax = -Infinity;
128 for (var curves in test) {
129 var curve = test[curves];
caryclark1049f122015-04-20 08:31:59 -0700130 var last = curve.length - (curve.length % 2 == 1 ? 1 : 0);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000131 for (var idx = 0; idx < last; idx += 2) {
132 xmin = Math.min(xmin, curve[idx]);
133 xmax = Math.max(xmax, curve[idx]);
134 ymin = Math.min(ymin, curve[idx + 1]);
135 ymax = Math.max(ymax, curve[idx + 1]);
136 }
137 }
caryclark54359292015-03-26 07:52:43 -0700138 xmin -= Math.min(1, Math.max(xmax - xmin, ymax - ymin));
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000139 var testW = xmax - xmin;
140 var testH = ymax - ymin;
141 subscale = 1;
142 while (testW * subscale < 0.1 && testH * subscale < 0.1) {
143 subscale *= 10;
144 }
145 while (testW * subscale > 10 && testH * subscale > 10) {
146 subscale /= 10;
147 }
148 setScale(xmin, xmax, ymin, ymax);
149 mouseX = (screenWidth / 2) / scale + srcLeft;
150 mouseY = (screenHeight / 2) / scale + srcTop;
151 initScale = scale;
152 }
153
154 function setScale(x0, x1, y0, y1) {
155 var srcWidth = x1 - x0;
156 var srcHeight = y1 - y0;
157 var usableWidth = screenWidth;
158 var xDigits = Math.ceil(Math.log(Math.abs(xmax)) / Math.log(10));
159 var yDigits = Math.ceil(Math.log(Math.abs(ymax)) / Math.log(10));
160 usableWidth -= (xDigits + yDigits) * 10;
161 usableWidth -= decimal_places * 10;
162 var hscale = usableWidth / srcWidth;
163 var vscale = screenHeight / srcHeight;
164 scale = Math.min(hscale, vscale);
165 var invScale = 1 / scale;
166 var sxmin = x0 - invScale * 5;
167 var symin = y0 - invScale * 10;
168 var sxmax = x1 + invScale * (6 * decimal_places + 10);
169 var symax = y1 + invScale * 10;
170 srcWidth = sxmax - sxmin;
171 srcHeight = symax - symin;
172 hscale = usableWidth / srcWidth;
173 vscale = screenHeight / srcHeight;
174 scale = Math.min(hscale, vscale);
175 srcLeft = sxmin;
176 srcTop = symin;
177 }
178
179function dxy_at_t(curve, t) {
180 var dxy = {};
181 if (curve.length == 6) {
182 var a = t - 1;
183 var b = 1 - 2 * t;
184 var c = t;
185 dxy.x = a * curve[0] + b * curve[2] + c * curve[4];
186 dxy.y = a * curve[1] + b * curve[3] + c * curve[5];
caryclark1049f122015-04-20 08:31:59 -0700187 } else if (curve.length == 7) {
188 var p20x = curve[4] - curve[0];
189 var p20y = curve[5] - curve[1];
190 var p10xw = (curve[2] - curve[0]) * curve[6];
191 var p10yw = (curve[3] - curve[1]) * curve[6];
192 var coeff0x = curve[6] * p20x - p20x;
193 var coeff0y = curve[6] * p20y - p20y;
194 var coeff1x = p20x - 2 * p10xw;
195 var coeff1y = p20y - 2 * p10yw;
196 dxy.x = t * (t * coeff0x + coeff1x) + p10xw;
197 dxy.y = t * (t * coeff0y + coeff1y) + p10yw;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000198 } else if (curve.length == 8) {
199 var one_t = 1 - t;
200 var a = curve[0];
201 var b = curve[2];
202 var c = curve[4];
203 var d = curve[6];
204 dxy.x = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
205 a = curve[1];
206 b = curve[3];
207 c = curve[5];
208 d = curve[7];
209 dxy.y = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
210 }
211 return dxy;
212}
213
214 var flt_epsilon = 1.19209290E-07;
215
216 function approximately_zero(A) {
217 return Math.abs(A) < flt_epsilon;
218 }
219
220 function approximately_zero_inverse(A) {
221 return Math.abs(A) > (1 / flt_epsilon);
222 }
223
224 function quad_real_roots(A, B, C) {
225 var s = [];
226 var p = B / (2 * A);
227 var q = C / A;
228 if (approximately_zero(A) && (approximately_zero_inverse(p)
229 || approximately_zero_inverse(q))) {
230 if (approximately_zero(B)) {
231 if (C == 0) {
232 s[0] = 0;
233 }
234 return s;
235 }
236 s[0] = -C / B;
237 return s;
238 }
239 /* normal form: x^2 + px + q = 0 */
240 var p2 = p * p;
241 if (!approximately_zero(p2 - q) && p2 < q) {
242 return s;
243 }
244 var sqrt_D = 0;
245 if (p2 > q) {
246 sqrt_D = Math.sqrt(p2 - q);
247 }
248 s[0] = sqrt_D - p;
249 var flip = -sqrt_D - p;
250 if (!approximately_zero(s[0] - flip)) {
251 s[1] = flip;
252 }
253 return s;
254 }
255
256 function cubic_real_roots(A, B, C, D) {
257 if (approximately_zero(A)) { // we're just a quadratic
258 return quad_real_roots(B, C, D);
259 }
260 if (approximately_zero(D)) { // 0 is one root
261 var s = quad_real_roots(A, B, C);
262 for (var i = 0; i < s.length; ++i) {
263 if (approximately_zero(s[i])) {
264 return s;
265 }
266 }
267 s.push(0);
268 return s;
269 }
270 if (approximately_zero(A + B + C + D)) { // 1 is one root
271 var s = quad_real_roots(A, A + B, -D);
272 for (var i = 0; i < s.length; ++i) {
273 if (approximately_zero(s[i] - 1)) {
274 return s;
275 }
276 }
277 s.push(1);
278 return s;
279 }
280 var a, b, c;
281 var invA = 1 / A;
282 a = B * invA;
283 b = C * invA;
284 c = D * invA;
285 var a2 = a * a;
286 var Q = (a2 - b * 3) / 9;
287 var R = (2 * a2 * a - 9 * a * b + 27 * c) / 54;
288 var R2 = R * R;
289 var Q3 = Q * Q * Q;
290 var R2MinusQ3 = R2 - Q3;
291 var adiv3 = a / 3;
292 var r;
293 var roots = [];
294 if (R2MinusQ3 < 0) { // we have 3 real roots
295 var theta = Math.acos(R / Math.sqrt(Q3));
296 var neg2RootQ = -2 * Math.sqrt(Q);
297 r = neg2RootQ * Math.cos(theta / 3) - adiv3;
298 roots.push(r);
299 r = neg2RootQ * Math.cos((theta + 2 * Math.PI) / 3) - adiv3;
300 if (!approximately_zero(roots[0] - r)) {
301 roots.push(r);
302 }
303 r = neg2RootQ * Math.cos((theta - 2 * Math.PI) / 3) - adiv3;
304 if (!approximately_zero(roots[0] - r) && (roots.length == 1
305 || !approximately_zero(roots[1] - r))) {
306 roots.push(r);
307 }
308 } else { // we have 1 real root
309 var sqrtR2MinusQ3 = Math.sqrt(R2MinusQ3);
310 var A = Math.abs(R) + sqrtR2MinusQ3;
311 A = Math.pow(A, 1/3);
312 if (R > 0) {
313 A = -A;
314 }
315 if (A != 0) {
316 A += Q / A;
317 }
318 r = A - adiv3;
319 roots.push(r);
320 if (approximately_zero(R2 - Q3)) {
321 r = -A / 2 - adiv3;
caryclark1049f122015-04-20 08:31:59 -0700322 if (!approximately_zero(roots[0] - r)) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000323 roots.push(r);
324 }
325 }
326 }
327 return roots;
328 }
329
330 function approximately_zero_or_more(tValue) {
331 return tValue >= -flt_epsilon;
332 }
333
334 function approximately_one_or_less(tValue) {
335 return tValue <= 1 + flt_epsilon;
336 }
337
338 function approximately_less_than_zero(tValue) {
339 return tValue < flt_epsilon;
340 }
341
342 function approximately_greater_than_one(tValue) {
343 return tValue > 1 - flt_epsilon;
344 }
345
346 function add_valid_ts(s) {
347 var t = [];
348 nextRoot:
349 for (var index = 0; index < s.length; ++index) {
350 var tValue = s[index];
351 if (approximately_zero_or_more(tValue) && approximately_one_or_less(tValue)) {
352 if (approximately_less_than_zero(tValue)) {
353 tValue = 0;
354 } else if (approximately_greater_than_one(tValue)) {
355 tValue = 1;
356 }
357 for (var idx2 = 0; idx2 < t.length; ++idx2) {
358 if (approximately_zero(t[idx2] - tValue)) {
359 continue nextRoot;
360 }
361 }
362 t.push(tValue);
363 }
364 }
365 return t;
366 }
367
368 function quad_roots(A, B, C) {
369 var s = quad_real_roots(A, B, C);
370 var foundRoots = add_valid_ts(s);
371 return foundRoots;
372 }
373
374 function cubic_roots(A, B, C, D) {
375 var s = cubic_real_roots(A, B, C, D);
376 var foundRoots = add_valid_ts(s);
377 return foundRoots;
378 }
379
380 function ray_curve_intersect(startPt, endPt, curve) {
381 var adj = endPt[0] - startPt[0];
382 var opp = endPt[1] - startPt[1];
383 var r = [];
caryclark1049f122015-04-20 08:31:59 -0700384 var len = (curve.length == 7 ? 6 : curve.length) / 2;
385 for (var n = 0; n < len; ++n) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000386 r[n] = (curve[n * 2 + 1] - startPt[1]) * adj - (curve[n * 2] - startPt[0]) * opp;
387 }
388 if (curve.length == 6) {
389 var A = r[2];
390 var B = r[1];
391 var C = r[0];
392 A += C - 2 * B; // A = a - 2*b + c
393 B -= C; // B = -(b - c)
394 return quad_roots(A, 2 * B, C);
395 }
caryclark1049f122015-04-20 08:31:59 -0700396 if (curve.length == 7) {
397 var A = r[2];
398 var B = r[1] * curve[6];
399 var C = r[0];
400 A += C - 2 * B; // A = a - 2*b + c
401 B -= C; // B = -(b - c)
402 return quad_roots(A, 2 * B, C);
403 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000404 var A = r[3]; // d
405 var B = r[2] * 3; // 3*c
406 var C = r[1] * 3; // 3*b
407 var D = r[0]; // a
408 A -= D - C + B; // A = -a + 3*b - 3*c + d
409 B += 3 * D - 2 * C; // B = 3*a - 6*b + 3*c
410 C -= 3 * D; // C = -3*a + 3*b
411 return cubic_roots(A, B, C, D);
412 }
413
414 function x_at_t(curve, t) {
415 var one_t = 1 - t;
416 if (curve.length == 4) {
417 return one_t * curve[0] + t * curve[2];
418 }
419 var one_t2 = one_t * one_t;
420 var t2 = t * t;
421 if (curve.length == 6) {
422 return one_t2 * curve[0] + 2 * one_t * t * curve[2] + t2 * curve[4];
423 }
caryclark1049f122015-04-20 08:31:59 -0700424 if (curve.length == 7) {
425 var numer = one_t2 * curve[0] + 2 * one_t * t * curve[2] * curve[6]
426 + t2 * curve[4];
427 var denom = one_t2 + 2 * one_t * t * curve[6]
428 + t2;
429 return numer / denom;
430 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000431 var a = one_t2 * one_t;
432 var b = 3 * one_t2 * t;
433 var c = 3 * one_t * t2;
434 var d = t2 * t;
435 return a * curve[0] + b * curve[2] + c * curve[4] + d * curve[6];
436 }
437
438 function y_at_t(curve, t) {
439 var one_t = 1 - t;
440 if (curve.length == 4) {
441 return one_t * curve[1] + t * curve[3];
442 }
443 var one_t2 = one_t * one_t;
444 var t2 = t * t;
445 if (curve.length == 6) {
446 return one_t2 * curve[1] + 2 * one_t * t * curve[3] + t2 * curve[5];
447 }
caryclark1049f122015-04-20 08:31:59 -0700448 if (curve.length == 7) {
449 var numer = one_t2 * curve[1] + 2 * one_t * t * curve[3] * curve[6]
450 + t2 * curve[5];
451 var denom = one_t2 + 2 * one_t * t * curve[6]
452 + t2;
453 return numer / denom;
454 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000455 var a = one_t2 * one_t;
456 var b = 3 * one_t2 * t;
457 var c = 3 * one_t * t2;
458 var d = t2 * t;
459 return a * curve[1] + b * curve[3] + c * curve[5] + d * curve[7];
460 }
461
462 function drawPointAtT(curve) {
463 var x = x_at_t(curve, curveT);
464 var y = y_at_t(curve, curveT);
caryclark55888e42016-07-18 10:01:36 -0700465 drawPoint(x, y, false);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000466 }
467
468 function drawLine(x1, y1, x2, y2) {
469 ctx.beginPath();
470 ctx.moveTo((x1 - srcLeft) * scale,
471 (y1 - srcTop) * scale);
472 ctx.lineTo((x2 - srcLeft) * scale,
473 (y2 - srcTop) * scale);
474 ctx.stroke();
475 }
476
caryclark55888e42016-07-18 10:01:36 -0700477 function drawPoint(px, py, xend) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000478 for (var pts = 0; pts < drawnPts.length; pts += 2) {
479 var x = drawnPts[pts];
480 var y = drawnPts[pts + 1];
481 if (px == x && py == y) {
482 return;
483 }
484 }
485 drawnPts.push(px);
486 drawnPts.push(py);
487 var _px = (px - srcLeft) * scale;
488 var _py = (py - srcTop) * scale;
489 ctx.beginPath();
caryclark55888e42016-07-18 10:01:36 -0700490 if (xend) {
491 ctx.moveTo(_px - 3, _py - 3);
492 ctx.lineTo(_px + 3, _py + 3);
493 ctx.moveTo(_px - 3, _py + 3);
494 ctx.lineTo(_px + 3, _py - 3);
495 } else {
496 ctx.arc(_px, _py, 3, 0, Math.PI * 2, true);
497 ctx.closePath();
498 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000499 ctx.stroke();
500 if (draw_point_xy) {
501 var label = px.toFixed(decimal_places) + ", " + py.toFixed(decimal_places);
502 ctx.font = "normal 10px Arial";
503 ctx.textAlign = "left";
504 ctx.fillStyle = "black";
505 ctx.fillText(label, _px + 5, _py);
506 }
507 }
508
509 function drawPointSolid(px, py) {
caryclark55888e42016-07-18 10:01:36 -0700510 drawPoint(px, py, false);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000511 ctx.fillStyle = "rgba(0,0,0, 0.4)";
512 ctx.fill();
513 }
514
515 function crossPt(origin, pt1, pt2) {
516 return ((pt1[0] - origin[0]) * (pt2[1] - origin[1])
517 - (pt1[1] - origin[1]) * (pt2[0] - origin[0])) > 0 ? 0 : 1;
518 }
519
520 // may not work well for cubics
521 function curveClosestT(curve, x, y) {
522 var closest = -1;
523 var closestDist = Infinity;
524 var l = Infinity, t = Infinity, r = -Infinity, b = -Infinity;
525 for (var i = 0; i < 16; ++i) {
526 var testX = x_at_t(curve, i / 16);
527 l = Math.min(testX, l);
528 r = Math.max(testX, r);
529 var testY = y_at_t(curve, i / 16);
530 t = Math.min(testY, t);
531 b = Math.max(testY, b);
532 var dx = testX - x;
533 var dy = testY - y;
534 var dist = dx * dx + dy * dy;
535 if (closestDist > dist) {
536 closestDist = dist;
537 closest = i;
538 }
539 }
540 var boundsX = r - l;
541 var boundsY = b - t;
542 var boundsDist = boundsX * boundsX + boundsY * boundsY;
543 if (closestDist > boundsDist) {
544 return -1;
545 }
546 console.log("closestDist = " + closestDist + " boundsDist = " + boundsDist
547 + " t = " + closest / 16);
548 return closest / 16;
549 }
550
caryclark1049f122015-04-20 08:31:59 -0700551 var kMaxConicToQuadPOW2 = 5;
552
553 function computeQuadPOW2(curve, tol) {
554 var a = curve[6] - 1;
555 var k = a / (4 * (2 + a));
556 var x = k * (curve[0] - 2 * curve[2] + curve[4]);
557 var y = k * (curve[1] - 2 * curve[3] + curve[5]);
558
559 var error = Math.sqrt(x * x + y * y);
560 var pow2;
561 for (pow2 = 0; pow2 < kMaxConicToQuadPOW2; ++pow2) {
562 if (error <= tol) {
563 break;
564 }
565 error *= 0.25;
566 }
567 return pow2;
568 }
569
570 function subdivide_w_value(w) {
571 return Math.sqrt(0.5 + w * 0.5);
572 }
573
574 function chop(curve, part1, part2) {
575 var w = curve[6];
576 var scale = 1 / (1 + w);
577 part1[0] = curve[0];
578 part1[1] = curve[1];
579 part1[2] = (curve[0] + curve[2] * w) * scale;
580 part1[3] = (curve[1] + curve[3] * w) * scale;
581 part1[4] = part2[0] = (curve[0] + (curve[2] * w) * 2 + curve[4]) * scale * 0.5;
582 part1[5] = part2[1] = (curve[1] + (curve[3] * w) * 2 + curve[5]) * scale * 0.5;
583 part2[2] = (curve[2] * w + curve[4]) * scale;
584 part2[3] = (curve[3] * w + curve[5]) * scale;
585 part2[4] = curve[4];
586 part2[5] = curve[5];
587 part1[6] = part2[6] = subdivide_w_value(w);
588 }
589
590 function subdivide(curve, level, pts) {
591 if (0 == level) {
592 pts.push(curve[2]);
593 pts.push(curve[3]);
594 pts.push(curve[4]);
595 pts.push(curve[5]);
596 } else {
597 var part1 = [], part2 = [];
598 chop(curve, part1, part2);
599 --level;
600 subdivide(part1, level, pts);
601 subdivide(part2, level, pts);
602 }
603 }
604
605 function chopIntoQuadsPOW2(curve, pow2, pts) {
606 subdivide(curve, pow2, pts);
607 return 1 << pow2;
608 }
609
610 function drawConic(curve, srcLeft, srcTop, scale) {
611 var tol = 1 / scale;
612 var pow2 = computeQuadPOW2(curve, tol);
613 var pts = [];
614 chopIntoQuadsPOW2(curve, pow2, pts);
615 for (var i = 0; i < pts.length; i += 4) {
616 ctx.quadraticCurveTo(
617 (pts[i + 0] - srcLeft) * scale, (pts[i + 1] - srcTop) * scale,
618 (pts[i + 2] - srcLeft) * scale, (pts[i + 3] - srcTop) * scale);
619 }
620 }
621
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000622 function draw(test, title) {
623 ctx.font = "normal 50px Arial";
624 ctx.textAlign = "left";
625 ctx.fillStyle = "rgba(0,0,0, 0.1)";
626 ctx.fillText(title, 50, 50);
627 ctx.font = "normal 10px Arial";
628 // ctx.lineWidth = "1.001"; "0.999";
629 var hullStarts = [];
630 var hullEnds = [];
631 var midSpokes = [];
632 var midDist = [];
633 var origin = [];
634 var shortSpokes = [];
635 var shortDist = [];
636 var sweeps = [];
637 drawnPts = [];
638 for (var curves in test) {
639 var curve = test[curves];
640 origin.push(curve[0]);
641 origin.push(curve[1]);
642 var startPt = [];
643 startPt.push(curve[2]);
644 startPt.push(curve[3]);
645 hullStarts.push(startPt);
646 var endPt = [];
647 if (curve.length == 4) {
648 endPt.push(curve[2]);
649 endPt.push(curve[3]);
caryclark1049f122015-04-20 08:31:59 -0700650 } else if (curve.length == 6 || curve.length == 7) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000651 endPt.push(curve[4]);
652 endPt.push(curve[5]);
653 } else if (curve.length == 8) {
654 endPt.push(curve[6]);
655 endPt.push(curve[7]);
656 }
657 hullEnds.push(endPt);
658 var sweep = crossPt(origin, startPt, endPt);
659 sweeps.push(sweep);
660 var midPt = [];
661 midPt.push(x_at_t(curve, 0.5));
662 midPt.push(y_at_t(curve, 0.5));
663 midSpokes.push(midPt);
664 var shortPt = [];
665 shortPt.push(x_at_t(curve, 0.25));
666 shortPt.push(y_at_t(curve, 0.25));
667 shortSpokes.push(shortPt);
668 var dx = midPt[0] - origin[0];
669 var dy = midPt[1] - origin[1];
670 var dist = Math.sqrt(dx * dx + dy * dy);
671 midDist.push(dist);
672 dx = shortPt[0] - origin[0];
673 dy = shortPt[1] - origin[1];
674 dist = Math.sqrt(dx * dx + dy * dy);
675 shortDist.push(dist);
676 }
677 var intersect = [];
678 var useIntersect = false;
679 var maxWidth = Math.max(xmax - xmin, ymax - ymin);
680 for (var curves in test) {
681 var curve = test[curves];
caryclark1049f122015-04-20 08:31:59 -0700682 if (curve.length >= 6 && curve.length <= 8) {
commit-bot@chromium.org8cb1daa2014-04-25 12:59:11 +0000683 var opp = curves == 0 || curves == 1 ? 0 : 1;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000684 var sects = ray_curve_intersect(origin, hullEnds[opp], curve);
685 intersect.push(sects);
686 if (sects.length > 1) {
687 var intersection = sects[0];
688 if (intersection == 0) {
689 intersection = sects[1];
690 }
691 var ix = x_at_t(curve, intersection) - origin[0];
692 var iy = y_at_t(curve, intersection) - origin[1];
693 var ex = hullEnds[opp][0] - origin[0];
694 var ey = hullEnds[opp][1] - origin[1];
695 if (ix * ex >= 0 && iy * ey >= 0) {
696 var iDist = Math.sqrt(ix * ix + iy * iy);
697 var eDist = Math.sqrt(ex * ex + ey * ey);
698 var delta = Math.abs(iDist - eDist) / maxWidth;
caryclark1049f122015-04-20 08:31:59 -0700699 if (delta > (curve.length != 8 ? 1e-5 : 1e-4)) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000700 useIntersect ^= true;
701 }
702 }
703 }
704 }
705 }
commit-bot@chromium.org8cb1daa2014-04-25 12:59:11 +0000706 var midLeft = curves != 0 ? crossPt(origin, midSpokes[0], midSpokes[1]) : 0;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000707 var firstInside;
708 if (useIntersect) {
709 var sect1 = intersect[0].length > 1;
710 var sIndex = sect1 ? 0 : 1;
711 var sects = intersect[sIndex];
712 var intersection = sects[0];
713 if (intersection == 0) {
714 intersection = sects[1];
715 }
716 var curve = test[sIndex];
717 var ix = x_at_t(curve, intersection) - origin[0];
718 var iy = y_at_t(curve, intersection) - origin[1];
719 var opp = sect1 ? 1 : 0;
720 var ex = hullEnds[opp][0] - origin[0];
721 var ey = hullEnds[opp][1] - origin[1];
722 var iDist = ix * ix + iy * iy;
723 var eDist = ex * ex + ey * ey;
724 firstInside = (iDist > eDist) ^ (sIndex == 0) ^ sweeps[0];
725// console.log("iDist=" + iDist + " eDist=" + eDist + " sIndex=" + sIndex
726 // + " sweeps[0]=" + sweeps[0]);
727 } else {
728 // console.log("midLeft=" + midLeft);
729 firstInside = midLeft != 0;
730 }
731 var shorter = midDist[1] < midDist[0];
732 var shortLeft = shorter ? crossPt(origin, shortSpokes[0], midSpokes[1])
733 : crossPt(origin, midSpokes[0], shortSpokes[1]);
734 var startCross = crossPt(origin, hullStarts[0], hullStarts[1]);
735 var disallowShort = midLeft == startCross && midLeft == sweeps[0]
736 && midLeft == sweeps[1];
737
738 // console.log("midLeft=" + midLeft + " startCross=" + startCross);
739 var intersectIndex = 0;
740 for (var curves in test) {
caryclark1049f122015-04-20 08:31:59 -0700741 var curve = test[draw_id != 2 ? curves : test.length - curves - 1];
742 if (curve.length != 4 && curve.length != 6 && curve.length != 7 && curve.length != 8) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000743 continue;
744 }
745 ctx.lineWidth = 1;
746 if (draw_tangents != 0) {
caryclarkfa6d6562014-07-29 12:13:28 -0700747 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000748 ctx.strokeStyle = "rgba(255,0,0, 0.3)";
749 } else {
750 ctx.strokeStyle = "rgba(0,0,255, 0.3)";
751 }
752 drawLine(curve[0], curve[1], curve[2], curve[3]);
753 if (draw_tangents != 2) {
754 if (curve.length > 4) drawLine(curve[2], curve[3], curve[4], curve[5]);
caryclark1049f122015-04-20 08:31:59 -0700755 if (curve.length == 8) drawLine(curve[4], curve[5], curve[6], curve[7]);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000756 }
757 if (draw_tangents != 1) {
caryclark1049f122015-04-20 08:31:59 -0700758 if (curve.length == 6 || curve.length == 7) {
759 drawLine(curve[0], curve[1], curve[4], curve[5]);
760 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000761 if (curve.length == 8) drawLine(curve[0], curve[1], curve[6], curve[7]);
762 }
763 }
764 ctx.beginPath();
765 ctx.moveTo((curve[0] - srcLeft) * scale, (curve[1] - srcTop) * scale);
766 if (curve.length == 4) {
767 ctx.lineTo((curve[2] - srcLeft) * scale, (curve[3] - srcTop) * scale);
768 } else if (curve.length == 6) {
769 ctx.quadraticCurveTo(
770 (curve[2] - srcLeft) * scale, (curve[3] - srcTop) * scale,
771 (curve[4] - srcLeft) * scale, (curve[5] - srcTop) * scale);
caryclark1049f122015-04-20 08:31:59 -0700772 } else if (curve.length == 7) {
773 drawConic(curve, srcLeft, srcTop, scale);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000774 } else {
775 ctx.bezierCurveTo(
776 (curve[2] - srcLeft) * scale, (curve[3] - srcTop) * scale,
777 (curve[4] - srcLeft) * scale, (curve[5] - srcTop) * scale,
778 (curve[6] - srcLeft) * scale, (curve[7] - srcTop) * scale);
779 }
caryclarkfa6d6562014-07-29 12:13:28 -0700780 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000781 ctx.strokeStyle = "rgba(255,0,0, 1)";
782 } else {
783 ctx.strokeStyle = "rgba(0,0,255, 1)";
784 }
785 ctx.stroke();
caryclark54359292015-03-26 07:52:43 -0700786 if (draw_endpoints > 0) {
caryclark55888e42016-07-18 10:01:36 -0700787 drawPoint(curve[0], curve[1], false);
caryclark54359292015-03-26 07:52:43 -0700788 if (draw_endpoints > 1 || curve.length == 4) {
caryclark55888e42016-07-18 10:01:36 -0700789 drawPoint(curve[2], curve[3], curve.length == 4 && draw_endpoints == 3);
caryclark54359292015-03-26 07:52:43 -0700790 }
caryclark1049f122015-04-20 08:31:59 -0700791 if (curve.length == 6 || curve.length == 7 ||
792 (draw_endpoints > 1 && curve.length == 8)) {
caryclark55888e42016-07-18 10:01:36 -0700793 drawPoint(curve[4], curve[5], (curve.length == 6 || curve.length == 7) && draw_endpoints == 3);
caryclark54359292015-03-26 07:52:43 -0700794 }
caryclark55888e42016-07-18 10:01:36 -0700795 if (curve.length == 8) {
796 drawPoint(curve[6], curve[7], curve.length == 8 && draw_endpoints == 3);
797 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000798 }
799 if (draw_midpoint != 0) {
800 if ((curves == 0) == (midLeft == 0)) {
801 ctx.strokeStyle = "rgba(0,180,127, 0.6)";
802 } else {
803 ctx.strokeStyle = "rgba(127,0,127, 0.6)";
804 }
805 var midX = x_at_t(curve, 0.5);
806 var midY = y_at_t(curve, 0.5);
807 drawPointSolid(midX, midY);
808 if (draw_midpoint > 1) {
809 drawLine(curve[0], curve[1], midX, midY);
810 }
811 }
812 if (draw_quarterpoint != 0) {
813 if ((curves == 0) == (shortLeft == 0)) {
814 ctx.strokeStyle = "rgba(0,191,63, 0.6)";
815 } else {
816 ctx.strokeStyle = "rgba(63,0,191, 0.6)";
817 }
818 var midT = (curves == 0) == shorter ? 0.25 : 0.5;
819 var midX = x_at_t(curve, midT);
820 var midY = y_at_t(curve, midT);
821 drawPointSolid(midX, midY);
822 if (draw_quarterpoint > 1) {
823 drawLine(curve[0], curve[1], midX, midY);
824 }
825 }
826 if (draw_sortpoint != 0) {
827 if ((curves == 0) == ((disallowShort == -1 ? midLeft : shortLeft) == 0)) {
828 ctx.strokeStyle = "rgba(0,155,37, 0.6)";
829 } else {
830 ctx.strokeStyle = "rgba(37,0,155, 0.6)";
831 }
832 var midT = (curves == 0) == shorter && disallowShort != curves ? 0.25 : 0.5;
833 console.log("curves=" + curves + " disallowShort=" + disallowShort
834 + " midLeft=" + midLeft + " shortLeft=" + shortLeft
835 + " shorter=" + shorter + " midT=" + midT);
836 var midX = x_at_t(curve, midT);
837 var midY = y_at_t(curve, midT);
838 drawPointSolid(midX, midY);
839 if (draw_sortpoint > 1) {
840 drawLine(curve[0], curve[1], midX, midY);
841 }
842 }
843 if (draw_ray_intersect != 0) {
844 ctx.strokeStyle = "rgba(75,45,199, 0.6)";
caryclark1049f122015-04-20 08:31:59 -0700845 if (curve.length >= 6 && curve.length <= 8) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000846 var intersections = intersect[intersectIndex];
847 for (var i in intersections) {
848 var intersection = intersections[i];
849 var x = x_at_t(curve, intersection);
850 var y = y_at_t(curve, intersection);
851 drawPointSolid(x, y);
852 if (draw_ray_intersect > 1) {
853 drawLine(curve[0], curve[1], x, y);
854 }
855 }
856 }
857 ++intersectIndex;
858 }
859 if (draw_order) {
860 var px = x_at_t(curve, 0.75);
861 var py = y_at_t(curve, 0.75);
862 var _px = (px - srcLeft) * scale;
863 var _py = (py - srcTop) * scale;
864 ctx.beginPath();
865 ctx.arc(_px, _py, 15, 0, Math.PI * 2, true);
866 ctx.closePath();
867 ctx.fillStyle = "white";
868 ctx.fill();
caryclarkfa6d6562014-07-29 12:13:28 -0700869 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000870 ctx.strokeStyle = "rgba(255,0,0, 1)";
871 ctx.fillStyle = "rgba(255,0,0, 1)";
872 } else {
873 ctx.strokeStyle = "rgba(0,0,255, 1)";
874 ctx.fillStyle = "rgba(0,0,255, 1)";
875 }
876 ctx.stroke();
877 ctx.font = "normal 16px Arial";
878 ctx.textAlign = "center";
879 ctx.fillText(parseInt(curves) + 1, _px, _py + 5);
880 }
881 if (draw_closest_t) {
882 var t = curveClosestT(curve, mouseX, mouseY);
883 if (t >= 0) {
884 var x = x_at_t(curve, t);
885 var y = y_at_t(curve, t);
886 drawPointSolid(x, y);
887 }
888 }
889 if (!approximately_zero(scale - initScale)) {
890 ctx.font = "normal 20px Arial";
891 ctx.fillStyle = "rgba(0,0,0, 0.3)";
892 ctx.textAlign = "right";
893 ctx.fillText(scale.toFixed(decimal_places) + 'x',
894 screenWidth - 10, screenHeight - 5);
895 }
896 if (draw_t) {
897 drawPointAtT(curve);
898 }
caryclark1049f122015-04-20 08:31:59 -0700899 if (draw_id != 0) {
caryclark54359292015-03-26 07:52:43 -0700900 var id = -1;
901 for (var i = 0; i < ids.length; i += 2) {
902 if (ids[i + 1] == curve) {
903 id = ids[i];
904 break;
905 }
906 }
907 if (id >= 0) {
908 var px = x_at_t(curve, 0.5);
909 var py = y_at_t(curve, 0.5);
910 var _px = (px - srcLeft) * scale;
911 var _py = (py - srcTop) * scale;
912 ctx.beginPath();
913 ctx.arc(_px, _py, 15, 0, Math.PI * 2, true);
914 ctx.closePath();
915 ctx.fillStyle = "white";
916 ctx.fill();
917 ctx.strokeStyle = "rgba(255,0,0, 1)";
918 ctx.fillStyle = "rgba(255,0,0, 1)";
919 ctx.stroke();
920 ctx.font = "normal 16px Arial";
921 ctx.textAlign = "center";
922 ctx.fillText(id, _px, _py + 5);
923 }
924 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000925 }
926 if (draw_t) {
927 drawCurveTControl();
928 }
caryclark1049f122015-04-20 08:31:59 -0700929 if (draw_w) {
930 drawCurveWControl();
931 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000932 }
933
934 function drawCurveTControl() {
935 ctx.lineWidth = 2;
936 ctx.strokeStyle = "rgba(0,0,0, 0.3)";
937 ctx.beginPath();
938 ctx.rect(screenWidth - 80, 40, 28, screenHeight - 80);
939 ctx.stroke();
940 var ty = 40 + curveT * (screenHeight - 80);
941 ctx.beginPath();
942 ctx.moveTo(screenWidth - 80, ty);
943 ctx.lineTo(screenWidth - 85, ty - 5);
944 ctx.lineTo(screenWidth - 85, ty + 5);
945 ctx.lineTo(screenWidth - 80, ty);
946 ctx.fillStyle = "rgba(0,0,0, 0.6)";
947 ctx.fill();
948 var num = curveT.toFixed(decimal_places);
949 ctx.font = "normal 10px Arial";
950 ctx.textAlign = "left";
951 ctx.fillText(num, screenWidth - 78, ty);
952 }
953
caryclark1049f122015-04-20 08:31:59 -0700954 function drawCurveWControl() {
955 var w = -1;
956 var choice = 0;
957 for (var curves in tests[testIndex]) {
958 var curve = tests[testIndex][curves];
959 if (curve.length != 7) {
960 continue;
961 }
962 if (choice == curveW) {
963 w = curve[6];
964 break;
965 }
966 ++choice;
967 }
968 if (w < 0) {
969 return;
970 }
971 ctx.lineWidth = 2;
972 ctx.strokeStyle = "rgba(0,0,0, 0.3)";
973 ctx.beginPath();
974 ctx.rect(screenWidth - 40, 40, 28, screenHeight - 80);
975 ctx.stroke();
976 var ty = 40 + w * (screenHeight - 80);
977 ctx.beginPath();
978 ctx.moveTo(screenWidth - 40, ty);
979 ctx.lineTo(screenWidth - 45, ty - 5);
980 ctx.lineTo(screenWidth - 45, ty + 5);
981 ctx.lineTo(screenWidth - 40, ty);
982 ctx.fillStyle = "rgba(0,0,0, 0.6)";
983 ctx.fill();
984 var num = w.toFixed(decimal_places);
985 ctx.font = "normal 10px Arial";
986 ctx.textAlign = "left";
987 ctx.fillText(num, screenWidth - 38, ty);
988 }
989
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000990 function ptInTControl() {
991 var e = window.event;
992 var tgt = e.target || e.srcElement;
993 var left = tgt.offsetLeft;
994 var top = tgt.offsetTop;
995 var x = (e.clientX - left);
996 var y = (e.clientY - top);
997 if (x < screenWidth - 80 || x > screenWidth - 50) {
998 return false;
999 }
1000 if (y < 40 || y > screenHeight - 80) {
1001 return false;
1002 }
1003 curveT = (y - 40) / (screenHeight - 120);
1004 if (curveT < 0 || curveT > 1) {
1005 throw "stop execution";
1006 }
1007 return true;
1008 }
1009
caryclark1049f122015-04-20 08:31:59 -07001010 function ptInWControl() {
1011 var e = window.event;
1012 var tgt = e.target || e.srcElement;
1013 var left = tgt.offsetLeft;
1014 var top = tgt.offsetTop;
1015 var x = (e.clientX - left);
1016 var y = (e.clientY - top);
1017 if (x < screenWidth - 40 || x > screenWidth - 10) {
1018 return false;
1019 }
1020 if (y < 40 || y > screenHeight - 80) {
1021 return false;
1022 }
1023 var w = (y - 40) / (screenHeight - 120);
1024 if (w < 0 || w > 1) {
1025 throw "stop execution";
1026 }
1027 var choice = 0;
1028 for (var curves in tests[testIndex]) {
1029 var curve = tests[testIndex][curves];
1030 if (curve.length != 7) {
1031 continue;
1032 }
1033 if (choice == curveW) {
1034 curve[6] = w;
1035 break;
1036 }
1037 ++choice;
1038 }
1039 return true;
1040 }
1041
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001042 function drawTop() {
1043 init(tests[testIndex]);
1044 redraw();
1045 }
1046
1047 function redraw() {
caryclark54359292015-03-26 07:52:43 -07001048 if (focus_on_selection > 0) {
1049 var focusXmin = focusYmin = Infinity;
1050 var focusXmax = focusYmax = -Infinity;
1051 var choice = 0;
1052 for (var curves in tests[testIndex]) {
1053 if (++choice != focus_on_selection) {
1054 continue;
1055 }
1056 var curve = tests[testIndex][curves];
caryclark1049f122015-04-20 08:31:59 -07001057 var last = curve.length - (curve.length % 2 == 1 ? 1 : 0);
caryclark54359292015-03-26 07:52:43 -07001058 for (var idx = 0; idx < last; idx += 2) {
1059 focusXmin = Math.min(focusXmin, curve[idx]);
1060 focusXmax = Math.max(focusXmax, curve[idx]);
1061 focusYmin = Math.min(focusYmin, curve[idx + 1]);
1062 focusYmax = Math.max(focusYmax, curve[idx + 1]);
1063 }
1064 }
1065 focusXmin -= Math.min(1, Math.max(focusXmax - focusXmin, focusYmax - focusYmin));
1066 if (focusXmin < focusXmax && focusYmin < focusYmax) {
1067 setScale(focusXmin, focusXmax, focusYmin, focusYmax);
1068 }
1069 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001070 ctx.beginPath();
1071 ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);
1072 ctx.fillStyle = "white";
1073 ctx.fill();
1074 draw(tests[testIndex], testTitles[testIndex]);
1075 }
1076
1077 function doKeyPress(evt) {
1078 var char = String.fromCharCode(evt.charCode);
caryclark54359292015-03-26 07:52:43 -07001079 var focusWasOn = false;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001080 switch (char) {
1081 case '0':
1082 case '1':
1083 case '2':
1084 case '3':
1085 case '4':
1086 case '5':
1087 case '6':
1088 case '7':
1089 case '8':
1090 case '9':
1091 decimal_places = char - '0';
1092 redraw();
1093 break;
1094 case '-':
caryclark54359292015-03-26 07:52:43 -07001095 focusWasOn = focus_on_selection;
1096 if (focusWasOn) {
1097 focus_on_selection = false;
1098 scale /= 1.2;
1099 } else {
1100 scale /= 2;
1101 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001102 calcLeftTop();
1103 redraw();
caryclark54359292015-03-26 07:52:43 -07001104 focus_on_selection = focusWasOn;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001105 break;
1106 case '=':
1107 case '+':
caryclark54359292015-03-26 07:52:43 -07001108 focusWasOn = focus_on_selection;
1109 if (focusWasOn) {
1110 focus_on_selection = false;
1111 scale *= 1.2;
1112 } else {
1113 scale *= 2;
1114 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001115 calcLeftTop();
1116 redraw();
caryclark54359292015-03-26 07:52:43 -07001117 focus_on_selection = focusWasOn;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001118 break;
caryclarkfa6d6562014-07-29 12:13:28 -07001119 case 'b':
1120 draw_cubic_red ^= true;
1121 redraw();
1122 break;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001123 case 'c':
1124 drawTop();
1125 break;
1126 case 'd':
1127 var test = tests[testIndex];
1128 var testClone = [];
1129 for (var curves in test) {
1130 var c = test[curves];
1131 var cClone = [];
1132 for (var index = 0; index < c.length; ++index) {
1133 cClone.push(c[index]);
1134 }
1135 testClone.push(cClone);
1136 }
1137 tests.push(testClone);
1138 testTitles.push(testTitles[testIndex] + " copy");
1139 testIndex = tests.length - 1;
1140 redraw();
1141 break;
1142 case 'e':
caryclark55888e42016-07-18 10:01:36 -07001143 draw_endpoints = (draw_endpoints + 1) % 4;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001144 redraw();
1145 break;
1146 case 'f':
1147 draw_derivative ^= true;
1148 redraw();
1149 break;
1150 case 'i':
1151 draw_ray_intersect = (draw_ray_intersect + 1) % 3;
1152 redraw();
1153 break;
1154 case 'l':
1155 var test = tests[testIndex];
1156 console.log("<div id=\"" + testTitles[testIndex] + "\" >");
1157 for (var curves in test) {
1158 var c = test[curves];
1159 var s = "{{";
1160 for (var i = 0; i < c.length; i += 2) {
1161 s += "{";
1162 s += c[i] + "," + c[i + 1];
1163 s += "}";
1164 if (i + 2 < c.length) {
1165 s += ", ";
1166 }
1167 }
1168 console.log(s + "}},");
1169 }
1170 console.log("</div>");
1171 break;
1172 case 'm':
1173 draw_midpoint = (draw_midpoint + 1) % 3;
1174 redraw();
1175 break;
1176 case 'N':
1177 testIndex += 9;
1178 case 'n':
1179 testIndex = (testIndex + 1) % tests.length;
1180 drawTop();
1181 break;
1182 case 'o':
1183 draw_order ^= true;
1184 redraw();
1185 break;
1186 case 'P':
1187 testIndex -= 9;
1188 case 'p':
1189 if (--testIndex < 0)
1190 testIndex = tests.length - 1;
1191 drawTop();
1192 break;
1193 case 'q':
1194 draw_quarterpoint = (draw_quarterpoint + 1) % 3;
1195 redraw();
1196 break;
1197 case 'r':
1198 for (var i = 0; i < testDivs.length; ++i) {
1199 var title = testDivs[i].id.toString();
1200 if (title == testTitles[testIndex]) {
1201 var str = testDivs[i].firstChild.data;
1202 parse(str, title);
1203 var original = tests.pop();
1204 testTitles.pop();
1205 tests[testIndex] = original;
1206 break;
1207 }
1208 }
1209 redraw();
1210 break;
1211 case 's':
1212 draw_sortpoint = (draw_sortpoint + 1) % 3;
1213 redraw();
1214 break;
1215 case 't':
1216 draw_t ^= true;
1217 redraw();
1218 break;
1219 case 'u':
1220 draw_closest_t ^= true;
1221 redraw();
1222 break;
1223 case 'v':
1224 draw_tangents = (draw_tangents + 1) % 4;
1225 redraw();
1226 break;
caryclark1049f122015-04-20 08:31:59 -07001227 case 'w':
1228 ++curveW;
1229 var choice = 0;
1230 draw_w = false;
1231 for (var curves in tests[testIndex]) {
1232 var curve = tests[testIndex][curves];
1233 if (curve.length != 7) {
1234 continue;
1235 }
1236 if (choice == curveW) {
1237 draw_w = true;
1238 break;
1239 }
1240 ++choice;
1241 }
1242 if (!draw_w) {
1243 curveW = -1;
1244 }
1245 redraw();
1246 break;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001247 case 'x':
1248 draw_point_xy ^= true;
1249 redraw();
1250 break;
1251 case 'y':
1252 draw_mouse_xy ^= true;
1253 redraw();
1254 break;
1255 case '\\':
1256 retina_scale ^= true;
1257 drawTop();
1258 break;
caryclark54359292015-03-26 07:52:43 -07001259 case '`':
1260 ++focus_on_selection;
1261 if (focus_on_selection >= tests[testIndex].length) {
1262 focus_on_selection = 0;
1263 }
1264 setScale(xmin, xmax, ymin, ymax);
1265 redraw();
1266 break;
1267 case '.':
caryclark1049f122015-04-20 08:31:59 -07001268 draw_id = (draw_id + 1) % 3;
caryclark54359292015-03-26 07:52:43 -07001269 redraw();
1270 break;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001271 }
1272 }
1273
1274 function doKeyDown(evt) {
1275 var char = evt.keyCode;
1276 var preventDefault = false;
1277 switch (char) {
1278 case 37: // left arrow
1279 if (evt.shiftKey) {
1280 testIndex -= 9;
1281 }
1282 if (--testIndex < 0)
1283 testIndex = tests.length - 1;
caryclark54359292015-03-26 07:52:43 -07001284 if (evt.ctrlKey) {
1285 redraw();
1286 } else {
1287 drawTop();
1288 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001289 preventDefault = true;
1290 break;
1291 case 39: // right arrow
1292 if (evt.shiftKey) {
1293 testIndex += 9;
1294 }
1295 if (++testIndex >= tests.length)
1296 testIndex = 0;
caryclark54359292015-03-26 07:52:43 -07001297 if (evt.ctrlKey) {
1298 redraw();
1299 } else {
1300 drawTop();
1301 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001302 preventDefault = true;
1303 break;
1304 }
1305 if (preventDefault) {
1306 evt.preventDefault();
1307 return false;
1308 }
1309 return true;
1310 }
1311
1312 function calcXY() {
1313 var e = window.event;
1314 var tgt = e.target || e.srcElement;
1315 var left = tgt.offsetLeft;
1316 var top = tgt.offsetTop;
1317 mouseX = (e.clientX - left) / scale + srcLeft;
1318 mouseY = (e.clientY - top) / scale + srcTop;
1319 }
1320
1321 function calcLeftTop() {
1322 srcLeft = mouseX - screenWidth / 2 / scale;
1323 srcTop = mouseY - screenHeight / 2 / scale;
1324 }
1325
1326 function handleMouseClick() {
caryclark1049f122015-04-20 08:31:59 -07001327 if ((!draw_t || !ptInTControl()) && (!draw_w || !ptInWControl())) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001328 calcXY();
1329 } else {
1330 redraw();
1331 }
1332 }
1333
1334 function initDown() {
1335 var test = tests[testIndex];
1336 var bestDistance = 1000000;
1337 activePt = -1;
1338 for (var curves in test) {
1339 var testCurve = test[curves];
caryclark1049f122015-04-20 08:31:59 -07001340 if (testCurve.length != 4 && (testCurve.length < 6 || testCurve.length > 8)) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001341 continue;
1342 }
caryclark1049f122015-04-20 08:31:59 -07001343 var testMax = testCurve.length == 7 ? 6 : testCurve.length;
1344 for (var i = 0; i < testMax; i += 2) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001345 var testX = testCurve[i];
1346 var testY = testCurve[i + 1];
1347 var dx = testX - mouseX;
1348 var dy = testY - mouseY;
1349 var dist = dx * dx + dy * dy;
1350 if (dist > bestDistance) {
1351 continue;
1352 }
1353 activeCurve = testCurve;
1354 activePt = i;
1355 bestDistance = dist;
1356 }
1357 }
1358 if (activePt >= 0) {
1359 lastX = mouseX;
1360 lastY = mouseY;
1361 }
1362 }
1363
1364 function handleMouseOver() {
1365 calcXY();
1366 if (draw_mouse_xy) {
1367 var num = mouseX.toFixed(decimal_places) + ", " + mouseY.toFixed(decimal_places);
1368 ctx.beginPath();
1369 ctx.rect(300, 100, num.length * 6, 10);
1370 ctx.fillStyle = "white";
1371 ctx.fill();
1372 ctx.font = "normal 10px Arial";
1373 ctx.fillStyle = "black";
1374 ctx.textAlign = "left";
1375 ctx.fillText(num, 300, 108);
1376 }
1377 if (!mouseDown) {
1378 activePt = -1;
1379 return;
1380 }
1381 if (activePt < 0) {
1382 initDown();
1383 return;
1384 }
1385 var deltaX = mouseX - lastX;
1386 var deltaY = mouseY - lastY;
1387 lastX = mouseX;
1388 lastY = mouseY;
1389 if (activePt == 0) {
1390 var test = tests[testIndex];
1391 for (var curves in test) {
1392 var testCurve = test[curves];
1393 testCurve[0] += deltaX;
1394 testCurve[1] += deltaY;
1395 }
1396 } else {
1397 activeCurve[activePt] += deltaX;
1398 activeCurve[activePt + 1] += deltaY;
1399 }
1400 redraw();
1401 }
1402
1403 function start() {
1404 for (var i = 0; i < testDivs.length; ++i) {
1405 var title = testDivs[i].id.toString();
1406 var str = testDivs[i].firstChild.data;
1407 parse(str, title);
1408 }
1409 drawTop();
1410 window.addEventListener('keypress', doKeyPress, true);
1411 window.addEventListener('keydown', doKeyDown, true);
1412 window.onresize = function () {
1413 drawTop();
1414 }
1415 }
1416
1417</script>
1418</head>
1419
1420<body onLoad="start();">
1421
1422<canvas id="canvas" width="750" height="500"
1423 onmousedown="mouseDown = true"
1424 onmouseup="mouseDown = false"
1425 onmousemove="handleMouseOver()"
1426 onclick="handleMouseClick()"
1427 ></canvas >
1428</body>
1429</html>