blob: 017d9165bf7eec7b07bf168c4804d72ab960bfe6 [file] [log] [blame]
Cary Clark4efcb7d2018-02-02 15:09:49 -05001<!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 Clark4efcb7d2018-02-02 15:09:49 -05009 <div id="cubics">
10 {{{0.00000000000000000, 0.00000000000000000 }, {0.00022939755581319332, 0.00022927834652364254 },{0.00022930106206331402, 0.00022929999977350235 }, {0.00022930069826543331, 0.00022913678549230099}}},
11 {{{0.00022930069826543331, 0.00022930069826543331 }, {0.00011465034913271666, 0.00011465034913271666 },{0.00011465061106719077, 0.00011460937093943357 }, {0.00014331332931760699, 0.00014325146912597120}}},
12 </div>
Cary Clark59d5a0e2017-01-23 14:38:52 +000013
caryclark34efb702016-10-24 08:19:06 -070014 </div>
15
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000016<script type="text/javascript">
17
caryclark34efb702016-10-24 08:19:06 -070018 var testDivs = [
Cary Clark59d5a0e2017-01-23 14:38:52 +000019 cubics,
caryclarkb36a3cd2016-10-18 07:59:44 -070020 ];
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000021
caryclark54359292015-03-26 07:52:43 -070022 var decimal_places = 3;
23
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000024 var tests = [];
25 var testTitles = [];
26 var testIndex = 0;
27 var ctx;
caryclark54359292015-03-26 07:52:43 -070028
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000029 var subscale = 1;
30 var xmin, xmax, ymin, ymax;
Cary Clark219b4e82017-05-26 11:36:49 -040031 var hscale, vscale;
32 var hinitScale, vinitScale;
33 var uniformScale = true;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000034 var mouseX, mouseY;
35 var mouseDown = false;
36 var srcLeft, srcTop;
37 var screenWidth, screenHeight;
38 var drawnPts;
39 var curveT = 0;
caryclark1049f122015-04-20 08:31:59 -070040 var curveW = -1;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000041
42 var lastX, lastY;
43 var activeCurve = [];
44 var activePt;
caryclark54359292015-03-26 07:52:43 -070045 var ids = [];
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000046
caryclark54359292015-03-26 07:52:43 -070047 var focus_on_selection = 0;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000048 var draw_t = false;
caryclark1049f122015-04-20 08:31:59 -070049 var draw_w = false;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000050 var draw_closest_t = false;
caryclarkfa6d6562014-07-29 12:13:28 -070051 var draw_cubic_red = false;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000052 var draw_derivative = false;
caryclark54359292015-03-26 07:52:43 -070053 var draw_endpoints = 2;
caryclark1049f122015-04-20 08:31:59 -070054 var draw_id = 0;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000055 var draw_midpoint = 0;
56 var draw_mouse_xy = false;
57 var draw_order = false;
58 var draw_point_xy = false;
59 var draw_ray_intersect = false;
60 var draw_quarterpoint = 0;
61 var draw_tangents = 1;
62 var draw_sortpoint = 0;
63 var retina_scale = !!window.devicePixelRatio;
64
65 function parse(test, title) {
66 var curveStrs = test.split("{{");
67 var pattern = /-?\d+\.*\d*e?-?\d*/g;
68 var curves = [];
69 for (var c in curveStrs) {
70 var curveStr = curveStrs[c];
caryclark54359292015-03-26 07:52:43 -070071 var idPart = curveStr.split("id=");
72 var id = -1;
73 if (idPart.length == 2) {
74 id = parseInt(idPart[1]);
75 curveStr = idPart[0];
76 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000077 var points = curveStr.match(pattern);
78 var pts = [];
79 for (var wd in points) {
80 var num = parseFloat(points[wd]);
81 if (isNaN(num)) continue;
82 pts.push(num);
83 }
caryclark54359292015-03-26 07:52:43 -070084 if (pts.length > 2) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000085 curves.push(pts);
caryclark54359292015-03-26 07:52:43 -070086 }
87 if (id >= 0) {
88 ids.push(id);
89 ids.push(pts);
90 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000091 }
92 if (curves.length >= 1) {
93 tests.push(curves);
94 testTitles.push(title);
95 }
96 }
97
98 function init(test) {
99 var canvas = document.getElementById('canvas');
100 if (!canvas.getContext) return;
101 ctx = canvas.getContext('2d');
102 var resScale = retina_scale && window.devicePixelRatio ? window.devicePixelRatio : 1;
103 var unscaledWidth = window.innerWidth - 20;
104 var unscaledHeight = window.innerHeight - 20;
105 screenWidth = unscaledWidth;
106 screenHeight = unscaledHeight;
107 canvas.width = unscaledWidth * resScale;
108 canvas.height = unscaledHeight * resScale;
109 canvas.style.width = unscaledWidth + 'px';
110 canvas.style.height = unscaledHeight + 'px';
111 if (resScale != 1) {
112 ctx.scale(resScale, resScale);
113 }
114 xmin = Infinity;
115 xmax = -Infinity;
116 ymin = Infinity;
117 ymax = -Infinity;
118 for (var curves in test) {
119 var curve = test[curves];
caryclark1049f122015-04-20 08:31:59 -0700120 var last = curve.length - (curve.length % 2 == 1 ? 1 : 0);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000121 for (var idx = 0; idx < last; idx += 2) {
122 xmin = Math.min(xmin, curve[idx]);
123 xmax = Math.max(xmax, curve[idx]);
124 ymin = Math.min(ymin, curve[idx + 1]);
125 ymax = Math.max(ymax, curve[idx + 1]);
126 }
127 }
caryclark54359292015-03-26 07:52:43 -0700128 xmin -= Math.min(1, Math.max(xmax - xmin, ymax - ymin));
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000129 var testW = xmax - xmin;
130 var testH = ymax - ymin;
131 subscale = 1;
132 while (testW * subscale < 0.1 && testH * subscale < 0.1) {
133 subscale *= 10;
134 }
135 while (testW * subscale > 10 && testH * subscale > 10) {
136 subscale /= 10;
137 }
138 setScale(xmin, xmax, ymin, ymax);
Cary Clark219b4e82017-05-26 11:36:49 -0400139 mouseX = (screenWidth / 2) / hscale + srcLeft;
140 mouseY = (screenHeight / 2) / vscale + srcTop;
141 hinitScale = hscale;
Ben Wagner29380bd2017-10-09 14:43:00 -0400142 vinitScale = vscale;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000143 }
144
145 function setScale(x0, x1, y0, y1) {
146 var srcWidth = x1 - x0;
147 var srcHeight = y1 - y0;
148 var usableWidth = screenWidth;
149 var xDigits = Math.ceil(Math.log(Math.abs(xmax)) / Math.log(10));
150 var yDigits = Math.ceil(Math.log(Math.abs(ymax)) / Math.log(10));
151 usableWidth -= (xDigits + yDigits) * 10;
152 usableWidth -= decimal_places * 10;
Cary Clark219b4e82017-05-26 11:36:49 -0400153 hscale = usableWidth / srcWidth;
154 vscale = screenHeight / srcHeight;
155 if (uniformScale) {
156 hscale = Math.min(hscale, vscale);
Ben Wagner29380bd2017-10-09 14:43:00 -0400157 vscale = hscale;
Cary Clark219b4e82017-05-26 11:36:49 -0400158 }
159 var hinvScale = 1 / hscale;
160 var vinvScale = 1 / vscale;
161 var sxmin = x0 - hinvScale * 5;
162 var symin = y0 - vinvScale * 10;
163 var sxmax = x1 + hinvScale * (6 * decimal_places + 10);
164 var symax = y1 + vinvScale * 10;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000165 srcWidth = sxmax - sxmin;
166 srcHeight = symax - symin;
167 hscale = usableWidth / srcWidth;
168 vscale = screenHeight / srcHeight;
Cary Clark219b4e82017-05-26 11:36:49 -0400169 if (uniformScale) {
170 hscale = Math.min(hscale, vscale);
171 vscale = hscale;
172 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000173 srcLeft = sxmin;
174 srcTop = symin;
175 }
176
177function dxy_at_t(curve, t) {
178 var dxy = {};
179 if (curve.length == 6) {
180 var a = t - 1;
181 var b = 1 - 2 * t;
182 var c = t;
183 dxy.x = a * curve[0] + b * curve[2] + c * curve[4];
184 dxy.y = a * curve[1] + b * curve[3] + c * curve[5];
caryclark1049f122015-04-20 08:31:59 -0700185 } else if (curve.length == 7) {
186 var p20x = curve[4] - curve[0];
187 var p20y = curve[5] - curve[1];
188 var p10xw = (curve[2] - curve[0]) * curve[6];
189 var p10yw = (curve[3] - curve[1]) * curve[6];
190 var coeff0x = curve[6] * p20x - p20x;
191 var coeff0y = curve[6] * p20y - p20y;
192 var coeff1x = p20x - 2 * p10xw;
193 var coeff1y = p20y - 2 * p10yw;
194 dxy.x = t * (t * coeff0x + coeff1x) + p10xw;
195 dxy.y = t * (t * coeff0y + coeff1y) + p10yw;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000196 } else if (curve.length == 8) {
197 var one_t = 1 - t;
198 var a = curve[0];
199 var b = curve[2];
200 var c = curve[4];
201 var d = curve[6];
202 dxy.x = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
203 a = curve[1];
204 b = curve[3];
205 c = curve[5];
206 d = curve[7];
207 dxy.y = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
208 }
209 return dxy;
210}
211
212 var flt_epsilon = 1.19209290E-07;
213
214 function approximately_zero(A) {
215 return Math.abs(A) < flt_epsilon;
216 }
217
218 function approximately_zero_inverse(A) {
219 return Math.abs(A) > (1 / flt_epsilon);
220 }
221
222 function quad_real_roots(A, B, C) {
223 var s = [];
224 var p = B / (2 * A);
225 var q = C / A;
226 if (approximately_zero(A) && (approximately_zero_inverse(p)
227 || approximately_zero_inverse(q))) {
228 if (approximately_zero(B)) {
229 if (C == 0) {
230 s[0] = 0;
231 }
232 return s;
233 }
234 s[0] = -C / B;
235 return s;
236 }
237 /* normal form: x^2 + px + q = 0 */
238 var p2 = p * p;
239 if (!approximately_zero(p2 - q) && p2 < q) {
240 return s;
241 }
242 var sqrt_D = 0;
243 if (p2 > q) {
244 sqrt_D = Math.sqrt(p2 - q);
245 }
246 s[0] = sqrt_D - p;
247 var flip = -sqrt_D - p;
248 if (!approximately_zero(s[0] - flip)) {
249 s[1] = flip;
250 }
251 return s;
252 }
253
254 function cubic_real_roots(A, B, C, D) {
255 if (approximately_zero(A)) { // we're just a quadratic
256 return quad_real_roots(B, C, D);
257 }
258 if (approximately_zero(D)) { // 0 is one root
259 var s = quad_real_roots(A, B, C);
260 for (var i = 0; i < s.length; ++i) {
261 if (approximately_zero(s[i])) {
262 return s;
263 }
264 }
265 s.push(0);
266 return s;
267 }
268 if (approximately_zero(A + B + C + D)) { // 1 is one root
269 var s = quad_real_roots(A, A + B, -D);
270 for (var i = 0; i < s.length; ++i) {
271 if (approximately_zero(s[i] - 1)) {
272 return s;
273 }
274 }
275 s.push(1);
276 return s;
277 }
278 var a, b, c;
279 var invA = 1 / A;
280 a = B * invA;
281 b = C * invA;
282 c = D * invA;
283 var a2 = a * a;
284 var Q = (a2 - b * 3) / 9;
285 var R = (2 * a2 * a - 9 * a * b + 27 * c) / 54;
286 var R2 = R * R;
287 var Q3 = Q * Q * Q;
288 var R2MinusQ3 = R2 - Q3;
289 var adiv3 = a / 3;
290 var r;
291 var roots = [];
292 if (R2MinusQ3 < 0) { // we have 3 real roots
293 var theta = Math.acos(R / Math.sqrt(Q3));
294 var neg2RootQ = -2 * Math.sqrt(Q);
295 r = neg2RootQ * Math.cos(theta / 3) - adiv3;
296 roots.push(r);
297 r = neg2RootQ * Math.cos((theta + 2 * Math.PI) / 3) - adiv3;
298 if (!approximately_zero(roots[0] - r)) {
299 roots.push(r);
300 }
301 r = neg2RootQ * Math.cos((theta - 2 * Math.PI) / 3) - adiv3;
302 if (!approximately_zero(roots[0] - r) && (roots.length == 1
303 || !approximately_zero(roots[1] - r))) {
304 roots.push(r);
305 }
306 } else { // we have 1 real root
307 var sqrtR2MinusQ3 = Math.sqrt(R2MinusQ3);
308 var A = Math.abs(R) + sqrtR2MinusQ3;
309 A = Math.pow(A, 1/3);
310 if (R > 0) {
311 A = -A;
312 }
313 if (A != 0) {
314 A += Q / A;
315 }
316 r = A - adiv3;
317 roots.push(r);
318 if (approximately_zero(R2 - Q3)) {
319 r = -A / 2 - adiv3;
caryclark1049f122015-04-20 08:31:59 -0700320 if (!approximately_zero(roots[0] - r)) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000321 roots.push(r);
322 }
323 }
324 }
325 return roots;
326 }
327
328 function approximately_zero_or_more(tValue) {
329 return tValue >= -flt_epsilon;
330 }
331
332 function approximately_one_or_less(tValue) {
333 return tValue <= 1 + flt_epsilon;
334 }
335
336 function approximately_less_than_zero(tValue) {
337 return tValue < flt_epsilon;
338 }
339
340 function approximately_greater_than_one(tValue) {
341 return tValue > 1 - flt_epsilon;
342 }
343
344 function add_valid_ts(s) {
345 var t = [];
346 nextRoot:
347 for (var index = 0; index < s.length; ++index) {
348 var tValue = s[index];
349 if (approximately_zero_or_more(tValue) && approximately_one_or_less(tValue)) {
350 if (approximately_less_than_zero(tValue)) {
351 tValue = 0;
352 } else if (approximately_greater_than_one(tValue)) {
353 tValue = 1;
354 }
355 for (var idx2 = 0; idx2 < t.length; ++idx2) {
356 if (approximately_zero(t[idx2] - tValue)) {
357 continue nextRoot;
358 }
359 }
360 t.push(tValue);
361 }
362 }
363 return t;
364 }
365
366 function quad_roots(A, B, C) {
367 var s = quad_real_roots(A, B, C);
368 var foundRoots = add_valid_ts(s);
369 return foundRoots;
370 }
371
372 function cubic_roots(A, B, C, D) {
373 var s = cubic_real_roots(A, B, C, D);
374 var foundRoots = add_valid_ts(s);
375 return foundRoots;
376 }
377
378 function ray_curve_intersect(startPt, endPt, curve) {
379 var adj = endPt[0] - startPt[0];
380 var opp = endPt[1] - startPt[1];
381 var r = [];
caryclark1049f122015-04-20 08:31:59 -0700382 var len = (curve.length == 7 ? 6 : curve.length) / 2;
383 for (var n = 0; n < len; ++n) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000384 r[n] = (curve[n * 2 + 1] - startPt[1]) * adj - (curve[n * 2] - startPt[0]) * opp;
385 }
386 if (curve.length == 6) {
387 var A = r[2];
388 var B = r[1];
389 var C = r[0];
390 A += C - 2 * B; // A = a - 2*b + c
391 B -= C; // B = -(b - c)
392 return quad_roots(A, 2 * B, C);
393 }
caryclark1049f122015-04-20 08:31:59 -0700394 if (curve.length == 7) {
395 var A = r[2];
396 var B = r[1] * curve[6];
397 var C = r[0];
398 A += C - 2 * B; // A = a - 2*b + c
399 B -= C; // B = -(b - c)
400 return quad_roots(A, 2 * B, C);
401 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000402 var A = r[3]; // d
403 var B = r[2] * 3; // 3*c
404 var C = r[1] * 3; // 3*b
405 var D = r[0]; // a
406 A -= D - C + B; // A = -a + 3*b - 3*c + d
407 B += 3 * D - 2 * C; // B = 3*a - 6*b + 3*c
408 C -= 3 * D; // C = -3*a + 3*b
409 return cubic_roots(A, B, C, D);
410 }
411
412 function x_at_t(curve, t) {
413 var one_t = 1 - t;
414 if (curve.length == 4) {
415 return one_t * curve[0] + t * curve[2];
416 }
417 var one_t2 = one_t * one_t;
418 var t2 = t * t;
419 if (curve.length == 6) {
420 return one_t2 * curve[0] + 2 * one_t * t * curve[2] + t2 * curve[4];
421 }
caryclark1049f122015-04-20 08:31:59 -0700422 if (curve.length == 7) {
423 var numer = one_t2 * curve[0] + 2 * one_t * t * curve[2] * curve[6]
424 + t2 * curve[4];
425 var denom = one_t2 + 2 * one_t * t * curve[6]
426 + t2;
427 return numer / denom;
428 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000429 var a = one_t2 * one_t;
430 var b = 3 * one_t2 * t;
431 var c = 3 * one_t * t2;
432 var d = t2 * t;
433 return a * curve[0] + b * curve[2] + c * curve[4] + d * curve[6];
434 }
435
436 function y_at_t(curve, t) {
437 var one_t = 1 - t;
438 if (curve.length == 4) {
439 return one_t * curve[1] + t * curve[3];
440 }
441 var one_t2 = one_t * one_t;
442 var t2 = t * t;
443 if (curve.length == 6) {
444 return one_t2 * curve[1] + 2 * one_t * t * curve[3] + t2 * curve[5];
445 }
caryclark1049f122015-04-20 08:31:59 -0700446 if (curve.length == 7) {
447 var numer = one_t2 * curve[1] + 2 * one_t * t * curve[3] * curve[6]
448 + t2 * curve[5];
449 var denom = one_t2 + 2 * one_t * t * curve[6]
450 + t2;
451 return numer / denom;
452 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000453 var a = one_t2 * one_t;
454 var b = 3 * one_t2 * t;
455 var c = 3 * one_t * t2;
456 var d = t2 * t;
457 return a * curve[1] + b * curve[3] + c * curve[5] + d * curve[7];
458 }
459
460 function drawPointAtT(curve) {
461 var x = x_at_t(curve, curveT);
462 var y = y_at_t(curve, curveT);
caryclark55888e42016-07-18 10:01:36 -0700463 drawPoint(x, y, false);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000464 }
465
466 function drawLine(x1, y1, x2, y2) {
467 ctx.beginPath();
Cary Clark219b4e82017-05-26 11:36:49 -0400468 ctx.moveTo((x1 - srcLeft) * hscale,
469 (y1 - srcTop) * vscale);
470 ctx.lineTo((x2 - srcLeft) * hscale,
471 (y2 - srcTop) * vscale);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000472 ctx.stroke();
473 }
474
caryclark55888e42016-07-18 10:01:36 -0700475 function drawPoint(px, py, xend) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000476 for (var pts = 0; pts < drawnPts.length; pts += 2) {
477 var x = drawnPts[pts];
478 var y = drawnPts[pts + 1];
479 if (px == x && py == y) {
480 return;
481 }
482 }
483 drawnPts.push(px);
484 drawnPts.push(py);
Cary Clark219b4e82017-05-26 11:36:49 -0400485 var _px = (px - srcLeft) * hscale;
486 var _py = (py - srcTop) * vscale;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000487 ctx.beginPath();
caryclark55888e42016-07-18 10:01:36 -0700488 if (xend) {
489 ctx.moveTo(_px - 3, _py - 3);
490 ctx.lineTo(_px + 3, _py + 3);
491 ctx.moveTo(_px - 3, _py + 3);
492 ctx.lineTo(_px + 3, _py - 3);
493 } else {
494 ctx.arc(_px, _py, 3, 0, Math.PI * 2, true);
495 ctx.closePath();
496 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000497 ctx.stroke();
498 if (draw_point_xy) {
499 var label = px.toFixed(decimal_places) + ", " + py.toFixed(decimal_places);
500 ctx.font = "normal 10px Arial";
501 ctx.textAlign = "left";
502 ctx.fillStyle = "black";
503 ctx.fillText(label, _px + 5, _py);
504 }
505 }
506
507 function drawPointSolid(px, py) {
caryclark55888e42016-07-18 10:01:36 -0700508 drawPoint(px, py, false);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000509 ctx.fillStyle = "rgba(0,0,0, 0.4)";
510 ctx.fill();
511 }
512
513 function crossPt(origin, pt1, pt2) {
514 return ((pt1[0] - origin[0]) * (pt2[1] - origin[1])
515 - (pt1[1] - origin[1]) * (pt2[0] - origin[0])) > 0 ? 0 : 1;
516 }
517
518 // may not work well for cubics
519 function curveClosestT(curve, x, y) {
520 var closest = -1;
521 var closestDist = Infinity;
522 var l = Infinity, t = Infinity, r = -Infinity, b = -Infinity;
523 for (var i = 0; i < 16; ++i) {
524 var testX = x_at_t(curve, i / 16);
525 l = Math.min(testX, l);
526 r = Math.max(testX, r);
527 var testY = y_at_t(curve, i / 16);
528 t = Math.min(testY, t);
529 b = Math.max(testY, b);
530 var dx = testX - x;
531 var dy = testY - y;
532 var dist = dx * dx + dy * dy;
533 if (closestDist > dist) {
534 closestDist = dist;
535 closest = i;
536 }
537 }
538 var boundsX = r - l;
539 var boundsY = b - t;
540 var boundsDist = boundsX * boundsX + boundsY * boundsY;
541 if (closestDist > boundsDist) {
542 return -1;
543 }
544 console.log("closestDist = " + closestDist + " boundsDist = " + boundsDist
545 + " t = " + closest / 16);
546 return closest / 16;
547 }
548
caryclark1049f122015-04-20 08:31:59 -0700549 var kMaxConicToQuadPOW2 = 5;
550
551 function computeQuadPOW2(curve, tol) {
552 var a = curve[6] - 1;
553 var k = a / (4 * (2 + a));
554 var x = k * (curve[0] - 2 * curve[2] + curve[4]);
555 var y = k * (curve[1] - 2 * curve[3] + curve[5]);
556
557 var error = Math.sqrt(x * x + y * y);
558 var pow2;
559 for (pow2 = 0; pow2 < kMaxConicToQuadPOW2; ++pow2) {
560 if (error <= tol) {
561 break;
562 }
563 error *= 0.25;
564 }
565 return pow2;
566 }
567
568 function subdivide_w_value(w) {
569 return Math.sqrt(0.5 + w * 0.5);
570 }
571
572 function chop(curve, part1, part2) {
573 var w = curve[6];
574 var scale = 1 / (1 + w);
575 part1[0] = curve[0];
576 part1[1] = curve[1];
577 part1[2] = (curve[0] + curve[2] * w) * scale;
578 part1[3] = (curve[1] + curve[3] * w) * scale;
579 part1[4] = part2[0] = (curve[0] + (curve[2] * w) * 2 + curve[4]) * scale * 0.5;
580 part1[5] = part2[1] = (curve[1] + (curve[3] * w) * 2 + curve[5]) * scale * 0.5;
581 part2[2] = (curve[2] * w + curve[4]) * scale;
582 part2[3] = (curve[3] * w + curve[5]) * scale;
583 part2[4] = curve[4];
584 part2[5] = curve[5];
585 part1[6] = part2[6] = subdivide_w_value(w);
586 }
587
588 function subdivide(curve, level, pts) {
589 if (0 == level) {
590 pts.push(curve[2]);
591 pts.push(curve[3]);
592 pts.push(curve[4]);
593 pts.push(curve[5]);
594 } else {
595 var part1 = [], part2 = [];
596 chop(curve, part1, part2);
597 --level;
598 subdivide(part1, level, pts);
599 subdivide(part2, level, pts);
600 }
601 }
602
603 function chopIntoQuadsPOW2(curve, pow2, pts) {
604 subdivide(curve, pow2, pts);
605 return 1 << pow2;
606 }
607
Cary Clark219b4e82017-05-26 11:36:49 -0400608 function drawConic(curve, srcLeft, srcTop, hscale, vscale) {
609 var tol = 1 / Math.min(hscale, vscale);
caryclark1049f122015-04-20 08:31:59 -0700610 var pow2 = computeQuadPOW2(curve, tol);
611 var pts = [];
612 chopIntoQuadsPOW2(curve, pow2, pts);
613 for (var i = 0; i < pts.length; i += 4) {
614 ctx.quadraticCurveTo(
Cary Clark219b4e82017-05-26 11:36:49 -0400615 (pts[i + 0] - srcLeft) * hscale, (pts[i + 1] - srcTop) * vscale,
616 (pts[i + 2] - srcLeft) * hscale, (pts[i + 3] - srcTop) * vscale);
caryclark1049f122015-04-20 08:31:59 -0700617 }
618 }
619
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000620 function draw(test, title) {
621 ctx.font = "normal 50px Arial";
622 ctx.textAlign = "left";
623 ctx.fillStyle = "rgba(0,0,0, 0.1)";
624 ctx.fillText(title, 50, 50);
625 ctx.font = "normal 10px Arial";
626 // ctx.lineWidth = "1.001"; "0.999";
627 var hullStarts = [];
628 var hullEnds = [];
629 var midSpokes = [];
630 var midDist = [];
631 var origin = [];
632 var shortSpokes = [];
633 var shortDist = [];
634 var sweeps = [];
635 drawnPts = [];
636 for (var curves in test) {
637 var curve = test[curves];
638 origin.push(curve[0]);
639 origin.push(curve[1]);
640 var startPt = [];
641 startPt.push(curve[2]);
642 startPt.push(curve[3]);
643 hullStarts.push(startPt);
644 var endPt = [];
645 if (curve.length == 4) {
646 endPt.push(curve[2]);
647 endPt.push(curve[3]);
caryclark1049f122015-04-20 08:31:59 -0700648 } else if (curve.length == 6 || curve.length == 7) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000649 endPt.push(curve[4]);
650 endPt.push(curve[5]);
651 } else if (curve.length == 8) {
652 endPt.push(curve[6]);
653 endPt.push(curve[7]);
654 }
655 hullEnds.push(endPt);
656 var sweep = crossPt(origin, startPt, endPt);
657 sweeps.push(sweep);
658 var midPt = [];
659 midPt.push(x_at_t(curve, 0.5));
660 midPt.push(y_at_t(curve, 0.5));
661 midSpokes.push(midPt);
662 var shortPt = [];
663 shortPt.push(x_at_t(curve, 0.25));
664 shortPt.push(y_at_t(curve, 0.25));
665 shortSpokes.push(shortPt);
666 var dx = midPt[0] - origin[0];
667 var dy = midPt[1] - origin[1];
668 var dist = Math.sqrt(dx * dx + dy * dy);
669 midDist.push(dist);
670 dx = shortPt[0] - origin[0];
671 dy = shortPt[1] - origin[1];
672 dist = Math.sqrt(dx * dx + dy * dy);
673 shortDist.push(dist);
674 }
675 var intersect = [];
676 var useIntersect = false;
677 var maxWidth = Math.max(xmax - xmin, ymax - ymin);
678 for (var curves in test) {
679 var curve = test[curves];
caryclark1049f122015-04-20 08:31:59 -0700680 if (curve.length >= 6 && curve.length <= 8) {
commit-bot@chromium.org8cb1daa2014-04-25 12:59:11 +0000681 var opp = curves == 0 || curves == 1 ? 0 : 1;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000682 var sects = ray_curve_intersect(origin, hullEnds[opp], curve);
683 intersect.push(sects);
684 if (sects.length > 1) {
685 var intersection = sects[0];
686 if (intersection == 0) {
687 intersection = sects[1];
688 }
689 var ix = x_at_t(curve, intersection) - origin[0];
690 var iy = y_at_t(curve, intersection) - origin[1];
691 var ex = hullEnds[opp][0] - origin[0];
692 var ey = hullEnds[opp][1] - origin[1];
693 if (ix * ex >= 0 && iy * ey >= 0) {
694 var iDist = Math.sqrt(ix * ix + iy * iy);
695 var eDist = Math.sqrt(ex * ex + ey * ey);
696 var delta = Math.abs(iDist - eDist) / maxWidth;
caryclark1049f122015-04-20 08:31:59 -0700697 if (delta > (curve.length != 8 ? 1e-5 : 1e-4)) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000698 useIntersect ^= true;
699 }
700 }
701 }
702 }
703 }
commit-bot@chromium.org8cb1daa2014-04-25 12:59:11 +0000704 var midLeft = curves != 0 ? crossPt(origin, midSpokes[0], midSpokes[1]) : 0;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000705 var firstInside;
706 if (useIntersect) {
707 var sect1 = intersect[0].length > 1;
708 var sIndex = sect1 ? 0 : 1;
709 var sects = intersect[sIndex];
710 var intersection = sects[0];
711 if (intersection == 0) {
712 intersection = sects[1];
713 }
714 var curve = test[sIndex];
715 var ix = x_at_t(curve, intersection) - origin[0];
716 var iy = y_at_t(curve, intersection) - origin[1];
717 var opp = sect1 ? 1 : 0;
718 var ex = hullEnds[opp][0] - origin[0];
719 var ey = hullEnds[opp][1] - origin[1];
720 var iDist = ix * ix + iy * iy;
721 var eDist = ex * ex + ey * ey;
722 firstInside = (iDist > eDist) ^ (sIndex == 0) ^ sweeps[0];
723// console.log("iDist=" + iDist + " eDist=" + eDist + " sIndex=" + sIndex
724 // + " sweeps[0]=" + sweeps[0]);
725 } else {
726 // console.log("midLeft=" + midLeft);
727 firstInside = midLeft != 0;
728 }
729 var shorter = midDist[1] < midDist[0];
730 var shortLeft = shorter ? crossPt(origin, shortSpokes[0], midSpokes[1])
731 : crossPt(origin, midSpokes[0], shortSpokes[1]);
732 var startCross = crossPt(origin, hullStarts[0], hullStarts[1]);
733 var disallowShort = midLeft == startCross && midLeft == sweeps[0]
734 && midLeft == sweeps[1];
735
736 // console.log("midLeft=" + midLeft + " startCross=" + startCross);
737 var intersectIndex = 0;
738 for (var curves in test) {
caryclark1049f122015-04-20 08:31:59 -0700739 var curve = test[draw_id != 2 ? curves : test.length - curves - 1];
740 if (curve.length != 4 && curve.length != 6 && curve.length != 7 && curve.length != 8) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000741 continue;
742 }
743 ctx.lineWidth = 1;
744 if (draw_tangents != 0) {
caryclarkfa6d6562014-07-29 12:13:28 -0700745 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000746 ctx.strokeStyle = "rgba(255,0,0, 0.3)";
747 } else {
748 ctx.strokeStyle = "rgba(0,0,255, 0.3)";
749 }
750 drawLine(curve[0], curve[1], curve[2], curve[3]);
751 if (draw_tangents != 2) {
752 if (curve.length > 4) drawLine(curve[2], curve[3], curve[4], curve[5]);
caryclark1049f122015-04-20 08:31:59 -0700753 if (curve.length == 8) drawLine(curve[4], curve[5], curve[6], curve[7]);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000754 }
755 if (draw_tangents != 1) {
caryclark1049f122015-04-20 08:31:59 -0700756 if (curve.length == 6 || curve.length == 7) {
757 drawLine(curve[0], curve[1], curve[4], curve[5]);
758 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000759 if (curve.length == 8) drawLine(curve[0], curve[1], curve[6], curve[7]);
760 }
761 }
762 ctx.beginPath();
Cary Clark219b4e82017-05-26 11:36:49 -0400763 ctx.moveTo((curve[0] - srcLeft) * hscale, (curve[1] - srcTop) * vscale);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000764 if (curve.length == 4) {
Cary Clark219b4e82017-05-26 11:36:49 -0400765 ctx.lineTo((curve[2] - srcLeft) * hscale, (curve[3] - srcTop) * vscale);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000766 } else if (curve.length == 6) {
767 ctx.quadraticCurveTo(
Cary Clark219b4e82017-05-26 11:36:49 -0400768 (curve[2] - srcLeft) * hscale, (curve[3] - srcTop) * vscale,
769 (curve[4] - srcLeft) * hscale, (curve[5] - srcTop) * vscale);
caryclark1049f122015-04-20 08:31:59 -0700770 } else if (curve.length == 7) {
Cary Clark219b4e82017-05-26 11:36:49 -0400771 drawConic(curve, srcLeft, srcTop, hscale, vscale);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000772 } else {
773 ctx.bezierCurveTo(
Cary Clark219b4e82017-05-26 11:36:49 -0400774 (curve[2] - srcLeft) * hscale, (curve[3] - srcTop) * vscale,
775 (curve[4] - srcLeft) * hscale, (curve[5] - srcTop) * vscale,
776 (curve[6] - srcLeft) * hscale, (curve[7] - srcTop) * vscale);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000777 }
caryclarkfa6d6562014-07-29 12:13:28 -0700778 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000779 ctx.strokeStyle = "rgba(255,0,0, 1)";
780 } else {
781 ctx.strokeStyle = "rgba(0,0,255, 1)";
782 }
783 ctx.stroke();
caryclark54359292015-03-26 07:52:43 -0700784 if (draw_endpoints > 0) {
caryclark55888e42016-07-18 10:01:36 -0700785 drawPoint(curve[0], curve[1], false);
caryclark54359292015-03-26 07:52:43 -0700786 if (draw_endpoints > 1 || curve.length == 4) {
caryclark55888e42016-07-18 10:01:36 -0700787 drawPoint(curve[2], curve[3], curve.length == 4 && draw_endpoints == 3);
caryclark54359292015-03-26 07:52:43 -0700788 }
caryclark1049f122015-04-20 08:31:59 -0700789 if (curve.length == 6 || curve.length == 7 ||
790 (draw_endpoints > 1 && curve.length == 8)) {
caryclark55888e42016-07-18 10:01:36 -0700791 drawPoint(curve[4], curve[5], (curve.length == 6 || curve.length == 7) && draw_endpoints == 3);
caryclark54359292015-03-26 07:52:43 -0700792 }
caryclark55888e42016-07-18 10:01:36 -0700793 if (curve.length == 8) {
794 drawPoint(curve[6], curve[7], curve.length == 8 && draw_endpoints == 3);
795 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000796 }
797 if (draw_midpoint != 0) {
798 if ((curves == 0) == (midLeft == 0)) {
799 ctx.strokeStyle = "rgba(0,180,127, 0.6)";
800 } else {
801 ctx.strokeStyle = "rgba(127,0,127, 0.6)";
802 }
803 var midX = x_at_t(curve, 0.5);
804 var midY = y_at_t(curve, 0.5);
805 drawPointSolid(midX, midY);
806 if (draw_midpoint > 1) {
807 drawLine(curve[0], curve[1], midX, midY);
808 }
809 }
810 if (draw_quarterpoint != 0) {
811 if ((curves == 0) == (shortLeft == 0)) {
812 ctx.strokeStyle = "rgba(0,191,63, 0.6)";
813 } else {
814 ctx.strokeStyle = "rgba(63,0,191, 0.6)";
815 }
816 var midT = (curves == 0) == shorter ? 0.25 : 0.5;
817 var midX = x_at_t(curve, midT);
818 var midY = y_at_t(curve, midT);
819 drawPointSolid(midX, midY);
820 if (draw_quarterpoint > 1) {
821 drawLine(curve[0], curve[1], midX, midY);
822 }
823 }
824 if (draw_sortpoint != 0) {
825 if ((curves == 0) == ((disallowShort == -1 ? midLeft : shortLeft) == 0)) {
826 ctx.strokeStyle = "rgba(0,155,37, 0.6)";
827 } else {
828 ctx.strokeStyle = "rgba(37,0,155, 0.6)";
829 }
830 var midT = (curves == 0) == shorter && disallowShort != curves ? 0.25 : 0.5;
831 console.log("curves=" + curves + " disallowShort=" + disallowShort
832 + " midLeft=" + midLeft + " shortLeft=" + shortLeft
833 + " shorter=" + shorter + " midT=" + midT);
834 var midX = x_at_t(curve, midT);
835 var midY = y_at_t(curve, midT);
836 drawPointSolid(midX, midY);
837 if (draw_sortpoint > 1) {
838 drawLine(curve[0], curve[1], midX, midY);
839 }
840 }
841 if (draw_ray_intersect != 0) {
842 ctx.strokeStyle = "rgba(75,45,199, 0.6)";
caryclark1049f122015-04-20 08:31:59 -0700843 if (curve.length >= 6 && curve.length <= 8) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000844 var intersections = intersect[intersectIndex];
845 for (var i in intersections) {
846 var intersection = intersections[i];
847 var x = x_at_t(curve, intersection);
848 var y = y_at_t(curve, intersection);
849 drawPointSolid(x, y);
850 if (draw_ray_intersect > 1) {
851 drawLine(curve[0], curve[1], x, y);
852 }
853 }
854 }
855 ++intersectIndex;
856 }
857 if (draw_order) {
858 var px = x_at_t(curve, 0.75);
859 var py = y_at_t(curve, 0.75);
Cary Clark219b4e82017-05-26 11:36:49 -0400860 var _px = (px - srcLeft) * hscale;
861 var _py = (py - srcTop) * vscale;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000862 ctx.beginPath();
863 ctx.arc(_px, _py, 15, 0, Math.PI * 2, true);
864 ctx.closePath();
865 ctx.fillStyle = "white";
866 ctx.fill();
caryclarkfa6d6562014-07-29 12:13:28 -0700867 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000868 ctx.strokeStyle = "rgba(255,0,0, 1)";
869 ctx.fillStyle = "rgba(255,0,0, 1)";
870 } else {
871 ctx.strokeStyle = "rgba(0,0,255, 1)";
872 ctx.fillStyle = "rgba(0,0,255, 1)";
873 }
874 ctx.stroke();
875 ctx.font = "normal 16px Arial";
876 ctx.textAlign = "center";
877 ctx.fillText(parseInt(curves) + 1, _px, _py + 5);
878 }
879 if (draw_closest_t) {
880 var t = curveClosestT(curve, mouseX, mouseY);
881 if (t >= 0) {
882 var x = x_at_t(curve, t);
883 var y = y_at_t(curve, t);
884 drawPointSolid(x, y);
885 }
886 }
Cary Clark219b4e82017-05-26 11:36:49 -0400887 if (!approximately_zero(hscale - hinitScale)) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000888 ctx.font = "normal 20px Arial";
889 ctx.fillStyle = "rgba(0,0,0, 0.3)";
890 ctx.textAlign = "right";
Cary Clark219b4e82017-05-26 11:36:49 -0400891 var scaleTextOffset = hscale != vscale ? -25 : -5;
892 ctx.fillText(hscale.toFixed(decimal_places) + 'x',
893 screenWidth - 10, screenHeight - scaleTextOffset);
Ben Wagner29380bd2017-10-09 14:43:00 -0400894 if (hscale != vscale) {
Cary Clark219b4e82017-05-26 11:36:49 -0400895 ctx.fillText(vscale.toFixed(decimal_places) + 'y',
Ben Wagner29380bd2017-10-09 14:43:00 -0400896 screenWidth - 10, screenHeight - 5);
897 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000898 }
899 if (draw_t) {
900 drawPointAtT(curve);
901 }
caryclark1049f122015-04-20 08:31:59 -0700902 if (draw_id != 0) {
caryclark54359292015-03-26 07:52:43 -0700903 var id = -1;
904 for (var i = 0; i < ids.length; i += 2) {
905 if (ids[i + 1] == curve) {
906 id = ids[i];
907 break;
908 }
909 }
910 if (id >= 0) {
911 var px = x_at_t(curve, 0.5);
912 var py = y_at_t(curve, 0.5);
Cary Clark219b4e82017-05-26 11:36:49 -0400913 var _px = (px - srcLeft) * hscale;
914 var _py = (py - srcTop) * vscale;
caryclark54359292015-03-26 07:52:43 -0700915 ctx.beginPath();
916 ctx.arc(_px, _py, 15, 0, Math.PI * 2, true);
917 ctx.closePath();
918 ctx.fillStyle = "white";
919 ctx.fill();
920 ctx.strokeStyle = "rgba(255,0,0, 1)";
921 ctx.fillStyle = "rgba(255,0,0, 1)";
922 ctx.stroke();
923 ctx.font = "normal 16px Arial";
924 ctx.textAlign = "center";
925 ctx.fillText(id, _px, _py + 5);
926 }
927 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000928 }
929 if (draw_t) {
930 drawCurveTControl();
931 }
caryclark1049f122015-04-20 08:31:59 -0700932 if (draw_w) {
933 drawCurveWControl();
934 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000935 }
936
937 function drawCurveTControl() {
938 ctx.lineWidth = 2;
939 ctx.strokeStyle = "rgba(0,0,0, 0.3)";
940 ctx.beginPath();
941 ctx.rect(screenWidth - 80, 40, 28, screenHeight - 80);
942 ctx.stroke();
943 var ty = 40 + curveT * (screenHeight - 80);
944 ctx.beginPath();
945 ctx.moveTo(screenWidth - 80, ty);
946 ctx.lineTo(screenWidth - 85, ty - 5);
947 ctx.lineTo(screenWidth - 85, ty + 5);
948 ctx.lineTo(screenWidth - 80, ty);
949 ctx.fillStyle = "rgba(0,0,0, 0.6)";
950 ctx.fill();
951 var num = curveT.toFixed(decimal_places);
952 ctx.font = "normal 10px Arial";
953 ctx.textAlign = "left";
954 ctx.fillText(num, screenWidth - 78, ty);
955 }
956
caryclark1049f122015-04-20 08:31:59 -0700957 function drawCurveWControl() {
958 var w = -1;
959 var choice = 0;
960 for (var curves in tests[testIndex]) {
961 var curve = tests[testIndex][curves];
962 if (curve.length != 7) {
963 continue;
964 }
965 if (choice == curveW) {
966 w = curve[6];
967 break;
968 }
969 ++choice;
970 }
971 if (w < 0) {
972 return;
973 }
974 ctx.lineWidth = 2;
975 ctx.strokeStyle = "rgba(0,0,0, 0.3)";
976 ctx.beginPath();
977 ctx.rect(screenWidth - 40, 40, 28, screenHeight - 80);
978 ctx.stroke();
979 var ty = 40 + w * (screenHeight - 80);
980 ctx.beginPath();
981 ctx.moveTo(screenWidth - 40, ty);
982 ctx.lineTo(screenWidth - 45, ty - 5);
983 ctx.lineTo(screenWidth - 45, ty + 5);
984 ctx.lineTo(screenWidth - 40, ty);
985 ctx.fillStyle = "rgba(0,0,0, 0.6)";
986 ctx.fill();
987 var num = w.toFixed(decimal_places);
988 ctx.font = "normal 10px Arial";
989 ctx.textAlign = "left";
990 ctx.fillText(num, screenWidth - 38, ty);
991 }
992
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000993 function ptInTControl() {
994 var e = window.event;
995 var tgt = e.target || e.srcElement;
996 var left = tgt.offsetLeft;
997 var top = tgt.offsetTop;
998 var x = (e.clientX - left);
999 var y = (e.clientY - top);
1000 if (x < screenWidth - 80 || x > screenWidth - 50) {
1001 return false;
1002 }
1003 if (y < 40 || y > screenHeight - 80) {
1004 return false;
1005 }
1006 curveT = (y - 40) / (screenHeight - 120);
1007 if (curveT < 0 || curveT > 1) {
1008 throw "stop execution";
1009 }
1010 return true;
1011 }
1012
caryclark1049f122015-04-20 08:31:59 -07001013 function ptInWControl() {
1014 var e = window.event;
1015 var tgt = e.target || e.srcElement;
1016 var left = tgt.offsetLeft;
1017 var top = tgt.offsetTop;
1018 var x = (e.clientX - left);
1019 var y = (e.clientY - top);
1020 if (x < screenWidth - 40 || x > screenWidth - 10) {
1021 return false;
1022 }
1023 if (y < 40 || y > screenHeight - 80) {
1024 return false;
1025 }
1026 var w = (y - 40) / (screenHeight - 120);
1027 if (w < 0 || w > 1) {
1028 throw "stop execution";
1029 }
1030 var choice = 0;
1031 for (var curves in tests[testIndex]) {
1032 var curve = tests[testIndex][curves];
1033 if (curve.length != 7) {
1034 continue;
1035 }
1036 if (choice == curveW) {
1037 curve[6] = w;
1038 break;
1039 }
1040 ++choice;
1041 }
1042 return true;
1043 }
1044
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001045 function drawTop() {
1046 init(tests[testIndex]);
1047 redraw();
1048 }
1049
1050 function redraw() {
caryclark54359292015-03-26 07:52:43 -07001051 if (focus_on_selection > 0) {
1052 var focusXmin = focusYmin = Infinity;
1053 var focusXmax = focusYmax = -Infinity;
1054 var choice = 0;
1055 for (var curves in tests[testIndex]) {
1056 if (++choice != focus_on_selection) {
1057 continue;
1058 }
1059 var curve = tests[testIndex][curves];
caryclark1049f122015-04-20 08:31:59 -07001060 var last = curve.length - (curve.length % 2 == 1 ? 1 : 0);
caryclark54359292015-03-26 07:52:43 -07001061 for (var idx = 0; idx < last; idx += 2) {
1062 focusXmin = Math.min(focusXmin, curve[idx]);
1063 focusXmax = Math.max(focusXmax, curve[idx]);
1064 focusYmin = Math.min(focusYmin, curve[idx + 1]);
1065 focusYmax = Math.max(focusYmax, curve[idx + 1]);
1066 }
1067 }
1068 focusXmin -= Math.min(1, Math.max(focusXmax - focusXmin, focusYmax - focusYmin));
1069 if (focusXmin < focusXmax && focusYmin < focusYmax) {
1070 setScale(focusXmin, focusXmax, focusYmin, focusYmax);
1071 }
1072 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001073 ctx.beginPath();
1074 ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);
1075 ctx.fillStyle = "white";
1076 ctx.fill();
1077 draw(tests[testIndex], testTitles[testIndex]);
1078 }
1079
1080 function doKeyPress(evt) {
1081 var char = String.fromCharCode(evt.charCode);
caryclark54359292015-03-26 07:52:43 -07001082 var focusWasOn = false;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001083 switch (char) {
1084 case '0':
1085 case '1':
1086 case '2':
1087 case '3':
1088 case '4':
1089 case '5':
1090 case '6':
1091 case '7':
1092 case '8':
1093 case '9':
1094 decimal_places = char - '0';
1095 redraw();
1096 break;
1097 case '-':
caryclark54359292015-03-26 07:52:43 -07001098 focusWasOn = focus_on_selection;
1099 if (focusWasOn) {
1100 focus_on_selection = false;
Cary Clark219b4e82017-05-26 11:36:49 -04001101 hscale /= 1.2;
Ben Wagner29380bd2017-10-09 14:43:00 -04001102 vscale /= 1.2;
caryclark54359292015-03-26 07:52:43 -07001103 } else {
Ben Wagner29380bd2017-10-09 14:43:00 -04001104 hscale /= 2;
1105 vscale /= 2;
caryclark54359292015-03-26 07:52:43 -07001106 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001107 calcLeftTop();
1108 redraw();
caryclark54359292015-03-26 07:52:43 -07001109 focus_on_selection = focusWasOn;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001110 break;
1111 case '=':
1112 case '+':
caryclark54359292015-03-26 07:52:43 -07001113 focusWasOn = focus_on_selection;
1114 if (focusWasOn) {
1115 focus_on_selection = false;
Ben Wagner29380bd2017-10-09 14:43:00 -04001116 hscale *= 1.2;
1117 vscale *= 1.2;
caryclark54359292015-03-26 07:52:43 -07001118 } else {
Ben Wagner29380bd2017-10-09 14:43:00 -04001119 hscale *= 2;
1120 vscale *= 2;
caryclark54359292015-03-26 07:52:43 -07001121 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001122 calcLeftTop();
1123 redraw();
caryclark54359292015-03-26 07:52:43 -07001124 focus_on_selection = focusWasOn;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001125 break;
caryclarkfa6d6562014-07-29 12:13:28 -07001126 case 'b':
1127 draw_cubic_red ^= true;
1128 redraw();
1129 break;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001130 case 'c':
1131 drawTop();
1132 break;
1133 case 'd':
1134 var test = tests[testIndex];
1135 var testClone = [];
1136 for (var curves in test) {
1137 var c = test[curves];
1138 var cClone = [];
1139 for (var index = 0; index < c.length; ++index) {
1140 cClone.push(c[index]);
1141 }
1142 testClone.push(cClone);
1143 }
1144 tests.push(testClone);
1145 testTitles.push(testTitles[testIndex] + " copy");
1146 testIndex = tests.length - 1;
1147 redraw();
1148 break;
1149 case 'e':
caryclark55888e42016-07-18 10:01:36 -07001150 draw_endpoints = (draw_endpoints + 1) % 4;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001151 redraw();
1152 break;
1153 case 'f':
1154 draw_derivative ^= true;
1155 redraw();
1156 break;
Cary Clark219b4e82017-05-26 11:36:49 -04001157 case 'g':
1158 hscale *= 1.2;
1159 calcLeftTop();
1160 redraw();
1161 break;
1162 case 'G':
1163 hscale /= 1.2;
1164 calcLeftTop();
1165 redraw();
1166 break;
1167 case 'h':
1168 vscale *= 1.2;
1169 calcLeftTop();
1170 redraw();
1171 break;
1172 case 'H':
1173 vscale /= 1.2;
1174 calcLeftTop();
1175 redraw();
1176 break;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001177 case 'i':
1178 draw_ray_intersect = (draw_ray_intersect + 1) % 3;
1179 redraw();
1180 break;
1181 case 'l':
1182 var test = tests[testIndex];
1183 console.log("<div id=\"" + testTitles[testIndex] + "\" >");
1184 for (var curves in test) {
1185 var c = test[curves];
1186 var s = "{{";
1187 for (var i = 0; i < c.length; i += 2) {
1188 s += "{";
1189 s += c[i] + "," + c[i + 1];
1190 s += "}";
1191 if (i + 2 < c.length) {
1192 s += ", ";
1193 }
1194 }
1195 console.log(s + "}},");
1196 }
1197 console.log("</div>");
1198 break;
1199 case 'm':
1200 draw_midpoint = (draw_midpoint + 1) % 3;
1201 redraw();
1202 break;
1203 case 'N':
1204 testIndex += 9;
1205 case 'n':
1206 testIndex = (testIndex + 1) % tests.length;
1207 drawTop();
1208 break;
1209 case 'o':
1210 draw_order ^= true;
1211 redraw();
1212 break;
1213 case 'P':
1214 testIndex -= 9;
1215 case 'p':
1216 if (--testIndex < 0)
1217 testIndex = tests.length - 1;
1218 drawTop();
1219 break;
1220 case 'q':
1221 draw_quarterpoint = (draw_quarterpoint + 1) % 3;
1222 redraw();
1223 break;
1224 case 'r':
1225 for (var i = 0; i < testDivs.length; ++i) {
1226 var title = testDivs[i].id.toString();
1227 if (title == testTitles[testIndex]) {
1228 var str = testDivs[i].firstChild.data;
1229 parse(str, title);
1230 var original = tests.pop();
1231 testTitles.pop();
1232 tests[testIndex] = original;
1233 break;
1234 }
1235 }
1236 redraw();
1237 break;
1238 case 's':
1239 draw_sortpoint = (draw_sortpoint + 1) % 3;
1240 redraw();
1241 break;
1242 case 't':
1243 draw_t ^= true;
1244 redraw();
1245 break;
1246 case 'u':
1247 draw_closest_t ^= true;
1248 redraw();
1249 break;
1250 case 'v':
1251 draw_tangents = (draw_tangents + 1) % 4;
1252 redraw();
1253 break;
caryclark1049f122015-04-20 08:31:59 -07001254 case 'w':
1255 ++curveW;
1256 var choice = 0;
1257 draw_w = false;
1258 for (var curves in tests[testIndex]) {
1259 var curve = tests[testIndex][curves];
1260 if (curve.length != 7) {
1261 continue;
1262 }
1263 if (choice == curveW) {
1264 draw_w = true;
1265 break;
1266 }
1267 ++choice;
1268 }
1269 if (!draw_w) {
1270 curveW = -1;
1271 }
1272 redraw();
1273 break;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001274 case 'x':
1275 draw_point_xy ^= true;
1276 redraw();
1277 break;
1278 case 'y':
1279 draw_mouse_xy ^= true;
1280 redraw();
1281 break;
1282 case '\\':
1283 retina_scale ^= true;
1284 drawTop();
1285 break;
caryclark54359292015-03-26 07:52:43 -07001286 case '`':
1287 ++focus_on_selection;
1288 if (focus_on_selection >= tests[testIndex].length) {
1289 focus_on_selection = 0;
1290 }
1291 setScale(xmin, xmax, ymin, ymax);
1292 redraw();
1293 break;
1294 case '.':
caryclark1049f122015-04-20 08:31:59 -07001295 draw_id = (draw_id + 1) % 3;
caryclark54359292015-03-26 07:52:43 -07001296 redraw();
1297 break;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001298 }
1299 }
1300
1301 function doKeyDown(evt) {
1302 var char = evt.keyCode;
1303 var preventDefault = false;
1304 switch (char) {
1305 case 37: // left arrow
1306 if (evt.shiftKey) {
1307 testIndex -= 9;
1308 }
1309 if (--testIndex < 0)
1310 testIndex = tests.length - 1;
caryclark54359292015-03-26 07:52:43 -07001311 if (evt.ctrlKey) {
1312 redraw();
1313 } else {
1314 drawTop();
1315 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001316 preventDefault = true;
1317 break;
1318 case 39: // right arrow
1319 if (evt.shiftKey) {
1320 testIndex += 9;
1321 }
1322 if (++testIndex >= tests.length)
1323 testIndex = 0;
caryclark54359292015-03-26 07:52:43 -07001324 if (evt.ctrlKey) {
1325 redraw();
1326 } else {
1327 drawTop();
1328 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001329 preventDefault = true;
1330 break;
1331 }
1332 if (preventDefault) {
1333 evt.preventDefault();
1334 return false;
1335 }
1336 return true;
1337 }
1338
1339 function calcXY() {
1340 var e = window.event;
1341 var tgt = e.target || e.srcElement;
1342 var left = tgt.offsetLeft;
1343 var top = tgt.offsetTop;
Cary Clark219b4e82017-05-26 11:36:49 -04001344 mouseX = (e.clientX - left) / hscale + srcLeft;
1345 mouseY = (e.clientY - top) / vscale + srcTop;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001346 }
1347
1348 function calcLeftTop() {
Cary Clark219b4e82017-05-26 11:36:49 -04001349 srcLeft = mouseX - screenWidth / 2 / hscale;
1350 srcTop = mouseY - screenHeight / 2 / vscale;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001351 }
1352
1353 function handleMouseClick() {
caryclark1049f122015-04-20 08:31:59 -07001354 if ((!draw_t || !ptInTControl()) && (!draw_w || !ptInWControl())) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001355 calcXY();
1356 } else {
1357 redraw();
1358 }
1359 }
1360
1361 function initDown() {
1362 var test = tests[testIndex];
1363 var bestDistance = 1000000;
1364 activePt = -1;
1365 for (var curves in test) {
1366 var testCurve = test[curves];
caryclark1049f122015-04-20 08:31:59 -07001367 if (testCurve.length != 4 && (testCurve.length < 6 || testCurve.length > 8)) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001368 continue;
1369 }
caryclark1049f122015-04-20 08:31:59 -07001370 var testMax = testCurve.length == 7 ? 6 : testCurve.length;
1371 for (var i = 0; i < testMax; i += 2) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001372 var testX = testCurve[i];
1373 var testY = testCurve[i + 1];
1374 var dx = testX - mouseX;
1375 var dy = testY - mouseY;
1376 var dist = dx * dx + dy * dy;
1377 if (dist > bestDistance) {
1378 continue;
1379 }
1380 activeCurve = testCurve;
1381 activePt = i;
1382 bestDistance = dist;
1383 }
1384 }
1385 if (activePt >= 0) {
1386 lastX = mouseX;
1387 lastY = mouseY;
1388 }
1389 }
1390
1391 function handleMouseOver() {
1392 calcXY();
1393 if (draw_mouse_xy) {
1394 var num = mouseX.toFixed(decimal_places) + ", " + mouseY.toFixed(decimal_places);
1395 ctx.beginPath();
1396 ctx.rect(300, 100, num.length * 6, 10);
1397 ctx.fillStyle = "white";
1398 ctx.fill();
1399 ctx.font = "normal 10px Arial";
1400 ctx.fillStyle = "black";
1401 ctx.textAlign = "left";
1402 ctx.fillText(num, 300, 108);
1403 }
1404 if (!mouseDown) {
1405 activePt = -1;
1406 return;
1407 }
1408 if (activePt < 0) {
1409 initDown();
1410 return;
1411 }
1412 var deltaX = mouseX - lastX;
1413 var deltaY = mouseY - lastY;
1414 lastX = mouseX;
1415 lastY = mouseY;
1416 if (activePt == 0) {
1417 var test = tests[testIndex];
1418 for (var curves in test) {
1419 var testCurve = test[curves];
1420 testCurve[0] += deltaX;
1421 testCurve[1] += deltaY;
1422 }
1423 } else {
1424 activeCurve[activePt] += deltaX;
1425 activeCurve[activePt + 1] += deltaY;
1426 }
1427 redraw();
1428 }
1429
1430 function start() {
1431 for (var i = 0; i < testDivs.length; ++i) {
1432 var title = testDivs[i].id.toString();
1433 var str = testDivs[i].firstChild.data;
1434 parse(str, title);
1435 }
1436 drawTop();
1437 window.addEventListener('keypress', doKeyPress, true);
1438 window.addEventListener('keydown', doKeyDown, true);
1439 window.onresize = function () {
1440 drawTop();
1441 }
1442 }
1443
1444</script>
1445</head>
1446
1447<body onLoad="start();">
1448
1449<canvas id="canvas" width="750" height="500"
1450 onmousedown="mouseDown = true"
1451 onmouseup="mouseDown = false"
1452 onmousemove="handleMouseOver()"
1453 onclick="handleMouseClick()"
1454 ></canvas >
1455</body>
1456</html>