blob: d8371ad9be8a908e34e48c9a2e7a0ebf6b3490ac [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">
Cary Clark219b4e82017-05-26 11:36:49 -040010{{{152, 16}, {152, 16.0685501}, {91.06044, 16.1242027}, {16, 16.1242027}}}, id=0
11{{{16, 16.1242027}, {-59.06044, 16.1242027}, {-120, 16.0685501}, {-120, 16}}}, id=1
12{{{-120, 16}, {-120, 15.9314508}, {-59.06044, 15.8757973}, {16, 15.8757973}}}, id=2
13{{{16, 15.8757973}, {91.06044, 15.8757973}, {152, 15.9314508}, {152, 16}}}, id=3
14{{{16, 16}, {152, 16}}}, id=4
15{{{16, 17}, {152, 17}}}, id=5
16{{{16, 16}, {16, 17}}}, id=6
17{{{152, 16}, {152, 17}}}, id=7
Cary Clark59d5a0e2017-01-23 14:38:52 +000018</div>
19
caryclark34efb702016-10-24 08:19:06 -070020 </div>
21
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000022<script type="text/javascript">
23
caryclark34efb702016-10-24 08:19:06 -070024 var testDivs = [
Cary Clark59d5a0e2017-01-23 14:38:52 +000025 cubics,
caryclarkb36a3cd2016-10-18 07:59:44 -070026 ];
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000027
caryclark54359292015-03-26 07:52:43 -070028 var decimal_places = 3;
29
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000030 var tests = [];
31 var testTitles = [];
32 var testIndex = 0;
33 var ctx;
caryclark54359292015-03-26 07:52:43 -070034
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000035 var subscale = 1;
36 var xmin, xmax, ymin, ymax;
Cary Clark219b4e82017-05-26 11:36:49 -040037 var hscale, vscale;
38 var hinitScale, vinitScale;
39 var uniformScale = true;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000040 var mouseX, mouseY;
41 var mouseDown = false;
42 var srcLeft, srcTop;
43 var screenWidth, screenHeight;
44 var drawnPts;
45 var curveT = 0;
caryclark1049f122015-04-20 08:31:59 -070046 var curveW = -1;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000047
48 var lastX, lastY;
49 var activeCurve = [];
50 var activePt;
caryclark54359292015-03-26 07:52:43 -070051 var ids = [];
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000052
caryclark54359292015-03-26 07:52:43 -070053 var focus_on_selection = 0;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000054 var draw_t = false;
caryclark1049f122015-04-20 08:31:59 -070055 var draw_w = false;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000056 var draw_closest_t = false;
caryclarkfa6d6562014-07-29 12:13:28 -070057 var draw_cubic_red = false;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000058 var draw_derivative = false;
caryclark54359292015-03-26 07:52:43 -070059 var draw_endpoints = 2;
caryclark1049f122015-04-20 08:31:59 -070060 var draw_id = 0;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000061 var draw_midpoint = 0;
62 var draw_mouse_xy = false;
63 var draw_order = false;
64 var draw_point_xy = false;
65 var draw_ray_intersect = false;
66 var draw_quarterpoint = 0;
67 var draw_tangents = 1;
68 var draw_sortpoint = 0;
69 var retina_scale = !!window.devicePixelRatio;
70
71 function parse(test, title) {
72 var curveStrs = test.split("{{");
73 var pattern = /-?\d+\.*\d*e?-?\d*/g;
74 var curves = [];
75 for (var c in curveStrs) {
76 var curveStr = curveStrs[c];
caryclark54359292015-03-26 07:52:43 -070077 var idPart = curveStr.split("id=");
78 var id = -1;
79 if (idPart.length == 2) {
80 id = parseInt(idPart[1]);
81 curveStr = idPart[0];
82 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000083 var points = curveStr.match(pattern);
84 var pts = [];
85 for (var wd in points) {
86 var num = parseFloat(points[wd]);
87 if (isNaN(num)) continue;
88 pts.push(num);
89 }
caryclark54359292015-03-26 07:52:43 -070090 if (pts.length > 2) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000091 curves.push(pts);
caryclark54359292015-03-26 07:52:43 -070092 }
93 if (id >= 0) {
94 ids.push(id);
95 ids.push(pts);
96 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000097 }
98 if (curves.length >= 1) {
99 tests.push(curves);
100 testTitles.push(title);
101 }
102 }
103
104 function init(test) {
105 var canvas = document.getElementById('canvas');
106 if (!canvas.getContext) return;
107 ctx = canvas.getContext('2d');
108 var resScale = retina_scale && window.devicePixelRatio ? window.devicePixelRatio : 1;
109 var unscaledWidth = window.innerWidth - 20;
110 var unscaledHeight = window.innerHeight - 20;
111 screenWidth = unscaledWidth;
112 screenHeight = unscaledHeight;
113 canvas.width = unscaledWidth * resScale;
114 canvas.height = unscaledHeight * resScale;
115 canvas.style.width = unscaledWidth + 'px';
116 canvas.style.height = unscaledHeight + 'px';
117 if (resScale != 1) {
118 ctx.scale(resScale, resScale);
119 }
120 xmin = Infinity;
121 xmax = -Infinity;
122 ymin = Infinity;
123 ymax = -Infinity;
124 for (var curves in test) {
125 var curve = test[curves];
caryclark1049f122015-04-20 08:31:59 -0700126 var last = curve.length - (curve.length % 2 == 1 ? 1 : 0);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000127 for (var idx = 0; idx < last; idx += 2) {
128 xmin = Math.min(xmin, curve[idx]);
129 xmax = Math.max(xmax, curve[idx]);
130 ymin = Math.min(ymin, curve[idx + 1]);
131 ymax = Math.max(ymax, curve[idx + 1]);
132 }
133 }
caryclark54359292015-03-26 07:52:43 -0700134 xmin -= Math.min(1, Math.max(xmax - xmin, ymax - ymin));
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000135 var testW = xmax - xmin;
136 var testH = ymax - ymin;
137 subscale = 1;
138 while (testW * subscale < 0.1 && testH * subscale < 0.1) {
139 subscale *= 10;
140 }
141 while (testW * subscale > 10 && testH * subscale > 10) {
142 subscale /= 10;
143 }
144 setScale(xmin, xmax, ymin, ymax);
Cary Clark219b4e82017-05-26 11:36:49 -0400145 mouseX = (screenWidth / 2) / hscale + srcLeft;
146 mouseY = (screenHeight / 2) / vscale + srcTop;
147 hinitScale = hscale;
148 vinitScale = vscale;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000149 }
150
151 function setScale(x0, x1, y0, y1) {
152 var srcWidth = x1 - x0;
153 var srcHeight = y1 - y0;
154 var usableWidth = screenWidth;
155 var xDigits = Math.ceil(Math.log(Math.abs(xmax)) / Math.log(10));
156 var yDigits = Math.ceil(Math.log(Math.abs(ymax)) / Math.log(10));
157 usableWidth -= (xDigits + yDigits) * 10;
158 usableWidth -= decimal_places * 10;
Cary Clark219b4e82017-05-26 11:36:49 -0400159 hscale = usableWidth / srcWidth;
160 vscale = screenHeight / srcHeight;
161 if (uniformScale) {
162 hscale = Math.min(hscale, vscale);
163 vscale = hscale;
164 }
165 var hinvScale = 1 / hscale;
166 var vinvScale = 1 / vscale;
167 var sxmin = x0 - hinvScale * 5;
168 var symin = y0 - vinvScale * 10;
169 var sxmax = x1 + hinvScale * (6 * decimal_places + 10);
170 var symax = y1 + vinvScale * 10;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000171 srcWidth = sxmax - sxmin;
172 srcHeight = symax - symin;
173 hscale = usableWidth / srcWidth;
174 vscale = screenHeight / srcHeight;
Cary Clark219b4e82017-05-26 11:36:49 -0400175 if (uniformScale) {
176 hscale = Math.min(hscale, vscale);
177 vscale = hscale;
178 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000179 srcLeft = sxmin;
180 srcTop = symin;
181 }
182
183function dxy_at_t(curve, t) {
184 var dxy = {};
185 if (curve.length == 6) {
186 var a = t - 1;
187 var b = 1 - 2 * t;
188 var c = t;
189 dxy.x = a * curve[0] + b * curve[2] + c * curve[4];
190 dxy.y = a * curve[1] + b * curve[3] + c * curve[5];
caryclark1049f122015-04-20 08:31:59 -0700191 } else if (curve.length == 7) {
192 var p20x = curve[4] - curve[0];
193 var p20y = curve[5] - curve[1];
194 var p10xw = (curve[2] - curve[0]) * curve[6];
195 var p10yw = (curve[3] - curve[1]) * curve[6];
196 var coeff0x = curve[6] * p20x - p20x;
197 var coeff0y = curve[6] * p20y - p20y;
198 var coeff1x = p20x - 2 * p10xw;
199 var coeff1y = p20y - 2 * p10yw;
200 dxy.x = t * (t * coeff0x + coeff1x) + p10xw;
201 dxy.y = t * (t * coeff0y + coeff1y) + p10yw;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000202 } else if (curve.length == 8) {
203 var one_t = 1 - t;
204 var a = curve[0];
205 var b = curve[2];
206 var c = curve[4];
207 var d = curve[6];
208 dxy.x = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
209 a = curve[1];
210 b = curve[3];
211 c = curve[5];
212 d = curve[7];
213 dxy.y = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
214 }
215 return dxy;
216}
217
218 var flt_epsilon = 1.19209290E-07;
219
220 function approximately_zero(A) {
221 return Math.abs(A) < flt_epsilon;
222 }
223
224 function approximately_zero_inverse(A) {
225 return Math.abs(A) > (1 / flt_epsilon);
226 }
227
228 function quad_real_roots(A, B, C) {
229 var s = [];
230 var p = B / (2 * A);
231 var q = C / A;
232 if (approximately_zero(A) && (approximately_zero_inverse(p)
233 || approximately_zero_inverse(q))) {
234 if (approximately_zero(B)) {
235 if (C == 0) {
236 s[0] = 0;
237 }
238 return s;
239 }
240 s[0] = -C / B;
241 return s;
242 }
243 /* normal form: x^2 + px + q = 0 */
244 var p2 = p * p;
245 if (!approximately_zero(p2 - q) && p2 < q) {
246 return s;
247 }
248 var sqrt_D = 0;
249 if (p2 > q) {
250 sqrt_D = Math.sqrt(p2 - q);
251 }
252 s[0] = sqrt_D - p;
253 var flip = -sqrt_D - p;
254 if (!approximately_zero(s[0] - flip)) {
255 s[1] = flip;
256 }
257 return s;
258 }
259
260 function cubic_real_roots(A, B, C, D) {
261 if (approximately_zero(A)) { // we're just a quadratic
262 return quad_real_roots(B, C, D);
263 }
264 if (approximately_zero(D)) { // 0 is one root
265 var s = quad_real_roots(A, B, C);
266 for (var i = 0; i < s.length; ++i) {
267 if (approximately_zero(s[i])) {
268 return s;
269 }
270 }
271 s.push(0);
272 return s;
273 }
274 if (approximately_zero(A + B + C + D)) { // 1 is one root
275 var s = quad_real_roots(A, A + B, -D);
276 for (var i = 0; i < s.length; ++i) {
277 if (approximately_zero(s[i] - 1)) {
278 return s;
279 }
280 }
281 s.push(1);
282 return s;
283 }
284 var a, b, c;
285 var invA = 1 / A;
286 a = B * invA;
287 b = C * invA;
288 c = D * invA;
289 var a2 = a * a;
290 var Q = (a2 - b * 3) / 9;
291 var R = (2 * a2 * a - 9 * a * b + 27 * c) / 54;
292 var R2 = R * R;
293 var Q3 = Q * Q * Q;
294 var R2MinusQ3 = R2 - Q3;
295 var adiv3 = a / 3;
296 var r;
297 var roots = [];
298 if (R2MinusQ3 < 0) { // we have 3 real roots
299 var theta = Math.acos(R / Math.sqrt(Q3));
300 var neg2RootQ = -2 * Math.sqrt(Q);
301 r = neg2RootQ * Math.cos(theta / 3) - adiv3;
302 roots.push(r);
303 r = neg2RootQ * Math.cos((theta + 2 * Math.PI) / 3) - adiv3;
304 if (!approximately_zero(roots[0] - r)) {
305 roots.push(r);
306 }
307 r = neg2RootQ * Math.cos((theta - 2 * Math.PI) / 3) - adiv3;
308 if (!approximately_zero(roots[0] - r) && (roots.length == 1
309 || !approximately_zero(roots[1] - r))) {
310 roots.push(r);
311 }
312 } else { // we have 1 real root
313 var sqrtR2MinusQ3 = Math.sqrt(R2MinusQ3);
314 var A = Math.abs(R) + sqrtR2MinusQ3;
315 A = Math.pow(A, 1/3);
316 if (R > 0) {
317 A = -A;
318 }
319 if (A != 0) {
320 A += Q / A;
321 }
322 r = A - adiv3;
323 roots.push(r);
324 if (approximately_zero(R2 - Q3)) {
325 r = -A / 2 - adiv3;
caryclark1049f122015-04-20 08:31:59 -0700326 if (!approximately_zero(roots[0] - r)) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000327 roots.push(r);
328 }
329 }
330 }
331 return roots;
332 }
333
334 function approximately_zero_or_more(tValue) {
335 return tValue >= -flt_epsilon;
336 }
337
338 function approximately_one_or_less(tValue) {
339 return tValue <= 1 + flt_epsilon;
340 }
341
342 function approximately_less_than_zero(tValue) {
343 return tValue < flt_epsilon;
344 }
345
346 function approximately_greater_than_one(tValue) {
347 return tValue > 1 - flt_epsilon;
348 }
349
350 function add_valid_ts(s) {
351 var t = [];
352 nextRoot:
353 for (var index = 0; index < s.length; ++index) {
354 var tValue = s[index];
355 if (approximately_zero_or_more(tValue) && approximately_one_or_less(tValue)) {
356 if (approximately_less_than_zero(tValue)) {
357 tValue = 0;
358 } else if (approximately_greater_than_one(tValue)) {
359 tValue = 1;
360 }
361 for (var idx2 = 0; idx2 < t.length; ++idx2) {
362 if (approximately_zero(t[idx2] - tValue)) {
363 continue nextRoot;
364 }
365 }
366 t.push(tValue);
367 }
368 }
369 return t;
370 }
371
372 function quad_roots(A, B, C) {
373 var s = quad_real_roots(A, B, C);
374 var foundRoots = add_valid_ts(s);
375 return foundRoots;
376 }
377
378 function cubic_roots(A, B, C, D) {
379 var s = cubic_real_roots(A, B, C, D);
380 var foundRoots = add_valid_ts(s);
381 return foundRoots;
382 }
383
384 function ray_curve_intersect(startPt, endPt, curve) {
385 var adj = endPt[0] - startPt[0];
386 var opp = endPt[1] - startPt[1];
387 var r = [];
caryclark1049f122015-04-20 08:31:59 -0700388 var len = (curve.length == 7 ? 6 : curve.length) / 2;
389 for (var n = 0; n < len; ++n) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000390 r[n] = (curve[n * 2 + 1] - startPt[1]) * adj - (curve[n * 2] - startPt[0]) * opp;
391 }
392 if (curve.length == 6) {
393 var A = r[2];
394 var B = r[1];
395 var C = r[0];
396 A += C - 2 * B; // A = a - 2*b + c
397 B -= C; // B = -(b - c)
398 return quad_roots(A, 2 * B, C);
399 }
caryclark1049f122015-04-20 08:31:59 -0700400 if (curve.length == 7) {
401 var A = r[2];
402 var B = r[1] * curve[6];
403 var C = r[0];
404 A += C - 2 * B; // A = a - 2*b + c
405 B -= C; // B = -(b - c)
406 return quad_roots(A, 2 * B, C);
407 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000408 var A = r[3]; // d
409 var B = r[2] * 3; // 3*c
410 var C = r[1] * 3; // 3*b
411 var D = r[0]; // a
412 A -= D - C + B; // A = -a + 3*b - 3*c + d
413 B += 3 * D - 2 * C; // B = 3*a - 6*b + 3*c
414 C -= 3 * D; // C = -3*a + 3*b
415 return cubic_roots(A, B, C, D);
416 }
417
418 function x_at_t(curve, t) {
419 var one_t = 1 - t;
420 if (curve.length == 4) {
421 return one_t * curve[0] + t * curve[2];
422 }
423 var one_t2 = one_t * one_t;
424 var t2 = t * t;
425 if (curve.length == 6) {
426 return one_t2 * curve[0] + 2 * one_t * t * curve[2] + t2 * curve[4];
427 }
caryclark1049f122015-04-20 08:31:59 -0700428 if (curve.length == 7) {
429 var numer = one_t2 * curve[0] + 2 * one_t * t * curve[2] * curve[6]
430 + t2 * curve[4];
431 var denom = one_t2 + 2 * one_t * t * curve[6]
432 + t2;
433 return numer / denom;
434 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000435 var a = one_t2 * one_t;
436 var b = 3 * one_t2 * t;
437 var c = 3 * one_t * t2;
438 var d = t2 * t;
439 return a * curve[0] + b * curve[2] + c * curve[4] + d * curve[6];
440 }
441
442 function y_at_t(curve, t) {
443 var one_t = 1 - t;
444 if (curve.length == 4) {
445 return one_t * curve[1] + t * curve[3];
446 }
447 var one_t2 = one_t * one_t;
448 var t2 = t * t;
449 if (curve.length == 6) {
450 return one_t2 * curve[1] + 2 * one_t * t * curve[3] + t2 * curve[5];
451 }
caryclark1049f122015-04-20 08:31:59 -0700452 if (curve.length == 7) {
453 var numer = one_t2 * curve[1] + 2 * one_t * t * curve[3] * curve[6]
454 + t2 * curve[5];
455 var denom = one_t2 + 2 * one_t * t * curve[6]
456 + t2;
457 return numer / denom;
458 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000459 var a = one_t2 * one_t;
460 var b = 3 * one_t2 * t;
461 var c = 3 * one_t * t2;
462 var d = t2 * t;
463 return a * curve[1] + b * curve[3] + c * curve[5] + d * curve[7];
464 }
465
466 function drawPointAtT(curve) {
467 var x = x_at_t(curve, curveT);
468 var y = y_at_t(curve, curveT);
caryclark55888e42016-07-18 10:01:36 -0700469 drawPoint(x, y, false);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000470 }
471
472 function drawLine(x1, y1, x2, y2) {
473 ctx.beginPath();
Cary Clark219b4e82017-05-26 11:36:49 -0400474 ctx.moveTo((x1 - srcLeft) * hscale,
475 (y1 - srcTop) * vscale);
476 ctx.lineTo((x2 - srcLeft) * hscale,
477 (y2 - srcTop) * vscale);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000478 ctx.stroke();
479 }
480
caryclark55888e42016-07-18 10:01:36 -0700481 function drawPoint(px, py, xend) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000482 for (var pts = 0; pts < drawnPts.length; pts += 2) {
483 var x = drawnPts[pts];
484 var y = drawnPts[pts + 1];
485 if (px == x && py == y) {
486 return;
487 }
488 }
489 drawnPts.push(px);
490 drawnPts.push(py);
Cary Clark219b4e82017-05-26 11:36:49 -0400491 var _px = (px - srcLeft) * hscale;
492 var _py = (py - srcTop) * vscale;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000493 ctx.beginPath();
caryclark55888e42016-07-18 10:01:36 -0700494 if (xend) {
495 ctx.moveTo(_px - 3, _py - 3);
496 ctx.lineTo(_px + 3, _py + 3);
497 ctx.moveTo(_px - 3, _py + 3);
498 ctx.lineTo(_px + 3, _py - 3);
499 } else {
500 ctx.arc(_px, _py, 3, 0, Math.PI * 2, true);
501 ctx.closePath();
502 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000503 ctx.stroke();
504 if (draw_point_xy) {
505 var label = px.toFixed(decimal_places) + ", " + py.toFixed(decimal_places);
506 ctx.font = "normal 10px Arial";
507 ctx.textAlign = "left";
508 ctx.fillStyle = "black";
509 ctx.fillText(label, _px + 5, _py);
510 }
511 }
512
513 function drawPointSolid(px, py) {
caryclark55888e42016-07-18 10:01:36 -0700514 drawPoint(px, py, false);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000515 ctx.fillStyle = "rgba(0,0,0, 0.4)";
516 ctx.fill();
517 }
518
519 function crossPt(origin, pt1, pt2) {
520 return ((pt1[0] - origin[0]) * (pt2[1] - origin[1])
521 - (pt1[1] - origin[1]) * (pt2[0] - origin[0])) > 0 ? 0 : 1;
522 }
523
524 // may not work well for cubics
525 function curveClosestT(curve, x, y) {
526 var closest = -1;
527 var closestDist = Infinity;
528 var l = Infinity, t = Infinity, r = -Infinity, b = -Infinity;
529 for (var i = 0; i < 16; ++i) {
530 var testX = x_at_t(curve, i / 16);
531 l = Math.min(testX, l);
532 r = Math.max(testX, r);
533 var testY = y_at_t(curve, i / 16);
534 t = Math.min(testY, t);
535 b = Math.max(testY, b);
536 var dx = testX - x;
537 var dy = testY - y;
538 var dist = dx * dx + dy * dy;
539 if (closestDist > dist) {
540 closestDist = dist;
541 closest = i;
542 }
543 }
544 var boundsX = r - l;
545 var boundsY = b - t;
546 var boundsDist = boundsX * boundsX + boundsY * boundsY;
547 if (closestDist > boundsDist) {
548 return -1;
549 }
550 console.log("closestDist = " + closestDist + " boundsDist = " + boundsDist
551 + " t = " + closest / 16);
552 return closest / 16;
553 }
554
caryclark1049f122015-04-20 08:31:59 -0700555 var kMaxConicToQuadPOW2 = 5;
556
557 function computeQuadPOW2(curve, tol) {
558 var a = curve[6] - 1;
559 var k = a / (4 * (2 + a));
560 var x = k * (curve[0] - 2 * curve[2] + curve[4]);
561 var y = k * (curve[1] - 2 * curve[3] + curve[5]);
562
563 var error = Math.sqrt(x * x + y * y);
564 var pow2;
565 for (pow2 = 0; pow2 < kMaxConicToQuadPOW2; ++pow2) {
566 if (error <= tol) {
567 break;
568 }
569 error *= 0.25;
570 }
571 return pow2;
572 }
573
574 function subdivide_w_value(w) {
575 return Math.sqrt(0.5 + w * 0.5);
576 }
577
578 function chop(curve, part1, part2) {
579 var w = curve[6];
580 var scale = 1 / (1 + w);
581 part1[0] = curve[0];
582 part1[1] = curve[1];
583 part1[2] = (curve[0] + curve[2] * w) * scale;
584 part1[3] = (curve[1] + curve[3] * w) * scale;
585 part1[4] = part2[0] = (curve[0] + (curve[2] * w) * 2 + curve[4]) * scale * 0.5;
586 part1[5] = part2[1] = (curve[1] + (curve[3] * w) * 2 + curve[5]) * scale * 0.5;
587 part2[2] = (curve[2] * w + curve[4]) * scale;
588 part2[3] = (curve[3] * w + curve[5]) * scale;
589 part2[4] = curve[4];
590 part2[5] = curve[5];
591 part1[6] = part2[6] = subdivide_w_value(w);
592 }
593
594 function subdivide(curve, level, pts) {
595 if (0 == level) {
596 pts.push(curve[2]);
597 pts.push(curve[3]);
598 pts.push(curve[4]);
599 pts.push(curve[5]);
600 } else {
601 var part1 = [], part2 = [];
602 chop(curve, part1, part2);
603 --level;
604 subdivide(part1, level, pts);
605 subdivide(part2, level, pts);
606 }
607 }
608
609 function chopIntoQuadsPOW2(curve, pow2, pts) {
610 subdivide(curve, pow2, pts);
611 return 1 << pow2;
612 }
613
Cary Clark219b4e82017-05-26 11:36:49 -0400614 function drawConic(curve, srcLeft, srcTop, hscale, vscale) {
615 var tol = 1 / Math.min(hscale, vscale);
caryclark1049f122015-04-20 08:31:59 -0700616 var pow2 = computeQuadPOW2(curve, tol);
617 var pts = [];
618 chopIntoQuadsPOW2(curve, pow2, pts);
619 for (var i = 0; i < pts.length; i += 4) {
620 ctx.quadraticCurveTo(
Cary Clark219b4e82017-05-26 11:36:49 -0400621 (pts[i + 0] - srcLeft) * hscale, (pts[i + 1] - srcTop) * vscale,
622 (pts[i + 2] - srcLeft) * hscale, (pts[i + 3] - srcTop) * vscale);
caryclark1049f122015-04-20 08:31:59 -0700623 }
624 }
625
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000626 function draw(test, title) {
627 ctx.font = "normal 50px Arial";
628 ctx.textAlign = "left";
629 ctx.fillStyle = "rgba(0,0,0, 0.1)";
630 ctx.fillText(title, 50, 50);
631 ctx.font = "normal 10px Arial";
632 // ctx.lineWidth = "1.001"; "0.999";
633 var hullStarts = [];
634 var hullEnds = [];
635 var midSpokes = [];
636 var midDist = [];
637 var origin = [];
638 var shortSpokes = [];
639 var shortDist = [];
640 var sweeps = [];
641 drawnPts = [];
642 for (var curves in test) {
643 var curve = test[curves];
644 origin.push(curve[0]);
645 origin.push(curve[1]);
646 var startPt = [];
647 startPt.push(curve[2]);
648 startPt.push(curve[3]);
649 hullStarts.push(startPt);
650 var endPt = [];
651 if (curve.length == 4) {
652 endPt.push(curve[2]);
653 endPt.push(curve[3]);
caryclark1049f122015-04-20 08:31:59 -0700654 } else if (curve.length == 6 || curve.length == 7) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000655 endPt.push(curve[4]);
656 endPt.push(curve[5]);
657 } else if (curve.length == 8) {
658 endPt.push(curve[6]);
659 endPt.push(curve[7]);
660 }
661 hullEnds.push(endPt);
662 var sweep = crossPt(origin, startPt, endPt);
663 sweeps.push(sweep);
664 var midPt = [];
665 midPt.push(x_at_t(curve, 0.5));
666 midPt.push(y_at_t(curve, 0.5));
667 midSpokes.push(midPt);
668 var shortPt = [];
669 shortPt.push(x_at_t(curve, 0.25));
670 shortPt.push(y_at_t(curve, 0.25));
671 shortSpokes.push(shortPt);
672 var dx = midPt[0] - origin[0];
673 var dy = midPt[1] - origin[1];
674 var dist = Math.sqrt(dx * dx + dy * dy);
675 midDist.push(dist);
676 dx = shortPt[0] - origin[0];
677 dy = shortPt[1] - origin[1];
678 dist = Math.sqrt(dx * dx + dy * dy);
679 shortDist.push(dist);
680 }
681 var intersect = [];
682 var useIntersect = false;
683 var maxWidth = Math.max(xmax - xmin, ymax - ymin);
684 for (var curves in test) {
685 var curve = test[curves];
caryclark1049f122015-04-20 08:31:59 -0700686 if (curve.length >= 6 && curve.length <= 8) {
commit-bot@chromium.org8cb1daa2014-04-25 12:59:11 +0000687 var opp = curves == 0 || curves == 1 ? 0 : 1;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000688 var sects = ray_curve_intersect(origin, hullEnds[opp], curve);
689 intersect.push(sects);
690 if (sects.length > 1) {
691 var intersection = sects[0];
692 if (intersection == 0) {
693 intersection = sects[1];
694 }
695 var ix = x_at_t(curve, intersection) - origin[0];
696 var iy = y_at_t(curve, intersection) - origin[1];
697 var ex = hullEnds[opp][0] - origin[0];
698 var ey = hullEnds[opp][1] - origin[1];
699 if (ix * ex >= 0 && iy * ey >= 0) {
700 var iDist = Math.sqrt(ix * ix + iy * iy);
701 var eDist = Math.sqrt(ex * ex + ey * ey);
702 var delta = Math.abs(iDist - eDist) / maxWidth;
caryclark1049f122015-04-20 08:31:59 -0700703 if (delta > (curve.length != 8 ? 1e-5 : 1e-4)) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000704 useIntersect ^= true;
705 }
706 }
707 }
708 }
709 }
commit-bot@chromium.org8cb1daa2014-04-25 12:59:11 +0000710 var midLeft = curves != 0 ? crossPt(origin, midSpokes[0], midSpokes[1]) : 0;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000711 var firstInside;
712 if (useIntersect) {
713 var sect1 = intersect[0].length > 1;
714 var sIndex = sect1 ? 0 : 1;
715 var sects = intersect[sIndex];
716 var intersection = sects[0];
717 if (intersection == 0) {
718 intersection = sects[1];
719 }
720 var curve = test[sIndex];
721 var ix = x_at_t(curve, intersection) - origin[0];
722 var iy = y_at_t(curve, intersection) - origin[1];
723 var opp = sect1 ? 1 : 0;
724 var ex = hullEnds[opp][0] - origin[0];
725 var ey = hullEnds[opp][1] - origin[1];
726 var iDist = ix * ix + iy * iy;
727 var eDist = ex * ex + ey * ey;
728 firstInside = (iDist > eDist) ^ (sIndex == 0) ^ sweeps[0];
729// console.log("iDist=" + iDist + " eDist=" + eDist + " sIndex=" + sIndex
730 // + " sweeps[0]=" + sweeps[0]);
731 } else {
732 // console.log("midLeft=" + midLeft);
733 firstInside = midLeft != 0;
734 }
735 var shorter = midDist[1] < midDist[0];
736 var shortLeft = shorter ? crossPt(origin, shortSpokes[0], midSpokes[1])
737 : crossPt(origin, midSpokes[0], shortSpokes[1]);
738 var startCross = crossPt(origin, hullStarts[0], hullStarts[1]);
739 var disallowShort = midLeft == startCross && midLeft == sweeps[0]
740 && midLeft == sweeps[1];
741
742 // console.log("midLeft=" + midLeft + " startCross=" + startCross);
743 var intersectIndex = 0;
744 for (var curves in test) {
caryclark1049f122015-04-20 08:31:59 -0700745 var curve = test[draw_id != 2 ? curves : test.length - curves - 1];
746 if (curve.length != 4 && curve.length != 6 && curve.length != 7 && curve.length != 8) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000747 continue;
748 }
749 ctx.lineWidth = 1;
750 if (draw_tangents != 0) {
caryclarkfa6d6562014-07-29 12:13:28 -0700751 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000752 ctx.strokeStyle = "rgba(255,0,0, 0.3)";
753 } else {
754 ctx.strokeStyle = "rgba(0,0,255, 0.3)";
755 }
756 drawLine(curve[0], curve[1], curve[2], curve[3]);
757 if (draw_tangents != 2) {
758 if (curve.length > 4) drawLine(curve[2], curve[3], curve[4], curve[5]);
caryclark1049f122015-04-20 08:31:59 -0700759 if (curve.length == 8) drawLine(curve[4], curve[5], curve[6], curve[7]);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000760 }
761 if (draw_tangents != 1) {
caryclark1049f122015-04-20 08:31:59 -0700762 if (curve.length == 6 || curve.length == 7) {
763 drawLine(curve[0], curve[1], curve[4], curve[5]);
764 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000765 if (curve.length == 8) drawLine(curve[0], curve[1], curve[6], curve[7]);
766 }
767 }
768 ctx.beginPath();
Cary Clark219b4e82017-05-26 11:36:49 -0400769 ctx.moveTo((curve[0] - srcLeft) * hscale, (curve[1] - srcTop) * vscale);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000770 if (curve.length == 4) {
Cary Clark219b4e82017-05-26 11:36:49 -0400771 ctx.lineTo((curve[2] - srcLeft) * hscale, (curve[3] - srcTop) * vscale);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000772 } else if (curve.length == 6) {
773 ctx.quadraticCurveTo(
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);
caryclark1049f122015-04-20 08:31:59 -0700776 } else if (curve.length == 7) {
Cary Clark219b4e82017-05-26 11:36:49 -0400777 drawConic(curve, srcLeft, srcTop, hscale, vscale);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000778 } else {
779 ctx.bezierCurveTo(
Cary Clark219b4e82017-05-26 11:36:49 -0400780 (curve[2] - srcLeft) * hscale, (curve[3] - srcTop) * vscale,
781 (curve[4] - srcLeft) * hscale, (curve[5] - srcTop) * vscale,
782 (curve[6] - srcLeft) * hscale, (curve[7] - srcTop) * vscale);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000783 }
caryclarkfa6d6562014-07-29 12:13:28 -0700784 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000785 ctx.strokeStyle = "rgba(255,0,0, 1)";
786 } else {
787 ctx.strokeStyle = "rgba(0,0,255, 1)";
788 }
789 ctx.stroke();
caryclark54359292015-03-26 07:52:43 -0700790 if (draw_endpoints > 0) {
caryclark55888e42016-07-18 10:01:36 -0700791 drawPoint(curve[0], curve[1], false);
caryclark54359292015-03-26 07:52:43 -0700792 if (draw_endpoints > 1 || curve.length == 4) {
caryclark55888e42016-07-18 10:01:36 -0700793 drawPoint(curve[2], curve[3], curve.length == 4 && draw_endpoints == 3);
caryclark54359292015-03-26 07:52:43 -0700794 }
caryclark1049f122015-04-20 08:31:59 -0700795 if (curve.length == 6 || curve.length == 7 ||
796 (draw_endpoints > 1 && curve.length == 8)) {
caryclark55888e42016-07-18 10:01:36 -0700797 drawPoint(curve[4], curve[5], (curve.length == 6 || curve.length == 7) && draw_endpoints == 3);
caryclark54359292015-03-26 07:52:43 -0700798 }
caryclark55888e42016-07-18 10:01:36 -0700799 if (curve.length == 8) {
800 drawPoint(curve[6], curve[7], curve.length == 8 && draw_endpoints == 3);
801 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000802 }
803 if (draw_midpoint != 0) {
804 if ((curves == 0) == (midLeft == 0)) {
805 ctx.strokeStyle = "rgba(0,180,127, 0.6)";
806 } else {
807 ctx.strokeStyle = "rgba(127,0,127, 0.6)";
808 }
809 var midX = x_at_t(curve, 0.5);
810 var midY = y_at_t(curve, 0.5);
811 drawPointSolid(midX, midY);
812 if (draw_midpoint > 1) {
813 drawLine(curve[0], curve[1], midX, midY);
814 }
815 }
816 if (draw_quarterpoint != 0) {
817 if ((curves == 0) == (shortLeft == 0)) {
818 ctx.strokeStyle = "rgba(0,191,63, 0.6)";
819 } else {
820 ctx.strokeStyle = "rgba(63,0,191, 0.6)";
821 }
822 var midT = (curves == 0) == shorter ? 0.25 : 0.5;
823 var midX = x_at_t(curve, midT);
824 var midY = y_at_t(curve, midT);
825 drawPointSolid(midX, midY);
826 if (draw_quarterpoint > 1) {
827 drawLine(curve[0], curve[1], midX, midY);
828 }
829 }
830 if (draw_sortpoint != 0) {
831 if ((curves == 0) == ((disallowShort == -1 ? midLeft : shortLeft) == 0)) {
832 ctx.strokeStyle = "rgba(0,155,37, 0.6)";
833 } else {
834 ctx.strokeStyle = "rgba(37,0,155, 0.6)";
835 }
836 var midT = (curves == 0) == shorter && disallowShort != curves ? 0.25 : 0.5;
837 console.log("curves=" + curves + " disallowShort=" + disallowShort
838 + " midLeft=" + midLeft + " shortLeft=" + shortLeft
839 + " shorter=" + shorter + " midT=" + midT);
840 var midX = x_at_t(curve, midT);
841 var midY = y_at_t(curve, midT);
842 drawPointSolid(midX, midY);
843 if (draw_sortpoint > 1) {
844 drawLine(curve[0], curve[1], midX, midY);
845 }
846 }
847 if (draw_ray_intersect != 0) {
848 ctx.strokeStyle = "rgba(75,45,199, 0.6)";
caryclark1049f122015-04-20 08:31:59 -0700849 if (curve.length >= 6 && curve.length <= 8) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000850 var intersections = intersect[intersectIndex];
851 for (var i in intersections) {
852 var intersection = intersections[i];
853 var x = x_at_t(curve, intersection);
854 var y = y_at_t(curve, intersection);
855 drawPointSolid(x, y);
856 if (draw_ray_intersect > 1) {
857 drawLine(curve[0], curve[1], x, y);
858 }
859 }
860 }
861 ++intersectIndex;
862 }
863 if (draw_order) {
864 var px = x_at_t(curve, 0.75);
865 var py = y_at_t(curve, 0.75);
Cary Clark219b4e82017-05-26 11:36:49 -0400866 var _px = (px - srcLeft) * hscale;
867 var _py = (py - srcTop) * vscale;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000868 ctx.beginPath();
869 ctx.arc(_px, _py, 15, 0, Math.PI * 2, true);
870 ctx.closePath();
871 ctx.fillStyle = "white";
872 ctx.fill();
caryclarkfa6d6562014-07-29 12:13:28 -0700873 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000874 ctx.strokeStyle = "rgba(255,0,0, 1)";
875 ctx.fillStyle = "rgba(255,0,0, 1)";
876 } else {
877 ctx.strokeStyle = "rgba(0,0,255, 1)";
878 ctx.fillStyle = "rgba(0,0,255, 1)";
879 }
880 ctx.stroke();
881 ctx.font = "normal 16px Arial";
882 ctx.textAlign = "center";
883 ctx.fillText(parseInt(curves) + 1, _px, _py + 5);
884 }
885 if (draw_closest_t) {
886 var t = curveClosestT(curve, mouseX, mouseY);
887 if (t >= 0) {
888 var x = x_at_t(curve, t);
889 var y = y_at_t(curve, t);
890 drawPointSolid(x, y);
891 }
892 }
Cary Clark219b4e82017-05-26 11:36:49 -0400893 if (!approximately_zero(hscale - hinitScale)) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000894 ctx.font = "normal 20px Arial";
895 ctx.fillStyle = "rgba(0,0,0, 0.3)";
896 ctx.textAlign = "right";
Cary Clark219b4e82017-05-26 11:36:49 -0400897 var scaleTextOffset = hscale != vscale ? -25 : -5;
898 ctx.fillText(hscale.toFixed(decimal_places) + 'x',
899 screenWidth - 10, screenHeight - scaleTextOffset);
900 if (hscale != vscale) {
901 ctx.fillText(vscale.toFixed(decimal_places) + 'y',
902 screenWidth - 10, screenHeight - 5);
903 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000904 }
905 if (draw_t) {
906 drawPointAtT(curve);
907 }
caryclark1049f122015-04-20 08:31:59 -0700908 if (draw_id != 0) {
caryclark54359292015-03-26 07:52:43 -0700909 var id = -1;
910 for (var i = 0; i < ids.length; i += 2) {
911 if (ids[i + 1] == curve) {
912 id = ids[i];
913 break;
914 }
915 }
916 if (id >= 0) {
917 var px = x_at_t(curve, 0.5);
918 var py = y_at_t(curve, 0.5);
Cary Clark219b4e82017-05-26 11:36:49 -0400919 var _px = (px - srcLeft) * hscale;
920 var _py = (py - srcTop) * vscale;
caryclark54359292015-03-26 07:52:43 -0700921 ctx.beginPath();
922 ctx.arc(_px, _py, 15, 0, Math.PI * 2, true);
923 ctx.closePath();
924 ctx.fillStyle = "white";
925 ctx.fill();
926 ctx.strokeStyle = "rgba(255,0,0, 1)";
927 ctx.fillStyle = "rgba(255,0,0, 1)";
928 ctx.stroke();
929 ctx.font = "normal 16px Arial";
930 ctx.textAlign = "center";
931 ctx.fillText(id, _px, _py + 5);
932 }
933 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000934 }
935 if (draw_t) {
936 drawCurveTControl();
937 }
caryclark1049f122015-04-20 08:31:59 -0700938 if (draw_w) {
939 drawCurveWControl();
940 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000941 }
942
943 function drawCurveTControl() {
944 ctx.lineWidth = 2;
945 ctx.strokeStyle = "rgba(0,0,0, 0.3)";
946 ctx.beginPath();
947 ctx.rect(screenWidth - 80, 40, 28, screenHeight - 80);
948 ctx.stroke();
949 var ty = 40 + curveT * (screenHeight - 80);
950 ctx.beginPath();
951 ctx.moveTo(screenWidth - 80, ty);
952 ctx.lineTo(screenWidth - 85, ty - 5);
953 ctx.lineTo(screenWidth - 85, ty + 5);
954 ctx.lineTo(screenWidth - 80, ty);
955 ctx.fillStyle = "rgba(0,0,0, 0.6)";
956 ctx.fill();
957 var num = curveT.toFixed(decimal_places);
958 ctx.font = "normal 10px Arial";
959 ctx.textAlign = "left";
960 ctx.fillText(num, screenWidth - 78, ty);
961 }
962
caryclark1049f122015-04-20 08:31:59 -0700963 function drawCurveWControl() {
964 var w = -1;
965 var choice = 0;
966 for (var curves in tests[testIndex]) {
967 var curve = tests[testIndex][curves];
968 if (curve.length != 7) {
969 continue;
970 }
971 if (choice == curveW) {
972 w = curve[6];
973 break;
974 }
975 ++choice;
976 }
977 if (w < 0) {
978 return;
979 }
980 ctx.lineWidth = 2;
981 ctx.strokeStyle = "rgba(0,0,0, 0.3)";
982 ctx.beginPath();
983 ctx.rect(screenWidth - 40, 40, 28, screenHeight - 80);
984 ctx.stroke();
985 var ty = 40 + w * (screenHeight - 80);
986 ctx.beginPath();
987 ctx.moveTo(screenWidth - 40, ty);
988 ctx.lineTo(screenWidth - 45, ty - 5);
989 ctx.lineTo(screenWidth - 45, ty + 5);
990 ctx.lineTo(screenWidth - 40, ty);
991 ctx.fillStyle = "rgba(0,0,0, 0.6)";
992 ctx.fill();
993 var num = w.toFixed(decimal_places);
994 ctx.font = "normal 10px Arial";
995 ctx.textAlign = "left";
996 ctx.fillText(num, screenWidth - 38, ty);
997 }
998
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000999 function ptInTControl() {
1000 var e = window.event;
1001 var tgt = e.target || e.srcElement;
1002 var left = tgt.offsetLeft;
1003 var top = tgt.offsetTop;
1004 var x = (e.clientX - left);
1005 var y = (e.clientY - top);
1006 if (x < screenWidth - 80 || x > screenWidth - 50) {
1007 return false;
1008 }
1009 if (y < 40 || y > screenHeight - 80) {
1010 return false;
1011 }
1012 curveT = (y - 40) / (screenHeight - 120);
1013 if (curveT < 0 || curveT > 1) {
1014 throw "stop execution";
1015 }
1016 return true;
1017 }
1018
caryclark1049f122015-04-20 08:31:59 -07001019 function ptInWControl() {
1020 var e = window.event;
1021 var tgt = e.target || e.srcElement;
1022 var left = tgt.offsetLeft;
1023 var top = tgt.offsetTop;
1024 var x = (e.clientX - left);
1025 var y = (e.clientY - top);
1026 if (x < screenWidth - 40 || x > screenWidth - 10) {
1027 return false;
1028 }
1029 if (y < 40 || y > screenHeight - 80) {
1030 return false;
1031 }
1032 var w = (y - 40) / (screenHeight - 120);
1033 if (w < 0 || w > 1) {
1034 throw "stop execution";
1035 }
1036 var choice = 0;
1037 for (var curves in tests[testIndex]) {
1038 var curve = tests[testIndex][curves];
1039 if (curve.length != 7) {
1040 continue;
1041 }
1042 if (choice == curveW) {
1043 curve[6] = w;
1044 break;
1045 }
1046 ++choice;
1047 }
1048 return true;
1049 }
1050
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001051 function drawTop() {
1052 init(tests[testIndex]);
1053 redraw();
1054 }
1055
1056 function redraw() {
caryclark54359292015-03-26 07:52:43 -07001057 if (focus_on_selection > 0) {
1058 var focusXmin = focusYmin = Infinity;
1059 var focusXmax = focusYmax = -Infinity;
1060 var choice = 0;
1061 for (var curves in tests[testIndex]) {
1062 if (++choice != focus_on_selection) {
1063 continue;
1064 }
1065 var curve = tests[testIndex][curves];
caryclark1049f122015-04-20 08:31:59 -07001066 var last = curve.length - (curve.length % 2 == 1 ? 1 : 0);
caryclark54359292015-03-26 07:52:43 -07001067 for (var idx = 0; idx < last; idx += 2) {
1068 focusXmin = Math.min(focusXmin, curve[idx]);
1069 focusXmax = Math.max(focusXmax, curve[idx]);
1070 focusYmin = Math.min(focusYmin, curve[idx + 1]);
1071 focusYmax = Math.max(focusYmax, curve[idx + 1]);
1072 }
1073 }
1074 focusXmin -= Math.min(1, Math.max(focusXmax - focusXmin, focusYmax - focusYmin));
1075 if (focusXmin < focusXmax && focusYmin < focusYmax) {
1076 setScale(focusXmin, focusXmax, focusYmin, focusYmax);
1077 }
1078 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001079 ctx.beginPath();
1080 ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);
1081 ctx.fillStyle = "white";
1082 ctx.fill();
1083 draw(tests[testIndex], testTitles[testIndex]);
1084 }
1085
1086 function doKeyPress(evt) {
1087 var char = String.fromCharCode(evt.charCode);
caryclark54359292015-03-26 07:52:43 -07001088 var focusWasOn = false;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001089 switch (char) {
1090 case '0':
1091 case '1':
1092 case '2':
1093 case '3':
1094 case '4':
1095 case '5':
1096 case '6':
1097 case '7':
1098 case '8':
1099 case '9':
1100 decimal_places = char - '0';
1101 redraw();
1102 break;
1103 case '-':
caryclark54359292015-03-26 07:52:43 -07001104 focusWasOn = focus_on_selection;
1105 if (focusWasOn) {
1106 focus_on_selection = false;
Cary Clark219b4e82017-05-26 11:36:49 -04001107 hscale /= 1.2;
1108 vscale /= 1.2;
caryclark54359292015-03-26 07:52:43 -07001109 } else {
Cary Clark219b4e82017-05-26 11:36:49 -04001110 hscale /= 2;
1111 vscale /= 2;
caryclark54359292015-03-26 07:52:43 -07001112 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001113 calcLeftTop();
1114 redraw();
caryclark54359292015-03-26 07:52:43 -07001115 focus_on_selection = focusWasOn;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001116 break;
1117 case '=':
1118 case '+':
caryclark54359292015-03-26 07:52:43 -07001119 focusWasOn = focus_on_selection;
1120 if (focusWasOn) {
1121 focus_on_selection = false;
Cary Clark219b4e82017-05-26 11:36:49 -04001122 hscale *= 1.2;
1123 vscale *= 1.2;
caryclark54359292015-03-26 07:52:43 -07001124 } else {
Cary Clark219b4e82017-05-26 11:36:49 -04001125 hscale *= 2;
1126 vscale *= 2;
caryclark54359292015-03-26 07:52:43 -07001127 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001128 calcLeftTop();
1129 redraw();
caryclark54359292015-03-26 07:52:43 -07001130 focus_on_selection = focusWasOn;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001131 break;
caryclarkfa6d6562014-07-29 12:13:28 -07001132 case 'b':
1133 draw_cubic_red ^= true;
1134 redraw();
1135 break;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001136 case 'c':
1137 drawTop();
1138 break;
1139 case 'd':
1140 var test = tests[testIndex];
1141 var testClone = [];
1142 for (var curves in test) {
1143 var c = test[curves];
1144 var cClone = [];
1145 for (var index = 0; index < c.length; ++index) {
1146 cClone.push(c[index]);
1147 }
1148 testClone.push(cClone);
1149 }
1150 tests.push(testClone);
1151 testTitles.push(testTitles[testIndex] + " copy");
1152 testIndex = tests.length - 1;
1153 redraw();
1154 break;
1155 case 'e':
caryclark55888e42016-07-18 10:01:36 -07001156 draw_endpoints = (draw_endpoints + 1) % 4;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001157 redraw();
1158 break;
1159 case 'f':
1160 draw_derivative ^= true;
1161 redraw();
1162 break;
Cary Clark219b4e82017-05-26 11:36:49 -04001163 case 'g':
1164 hscale *= 1.2;
1165 calcLeftTop();
1166 redraw();
1167 break;
1168 case 'G':
1169 hscale /= 1.2;
1170 calcLeftTop();
1171 redraw();
1172 break;
1173 case 'h':
1174 vscale *= 1.2;
1175 calcLeftTop();
1176 redraw();
1177 break;
1178 case 'H':
1179 vscale /= 1.2;
1180 calcLeftTop();
1181 redraw();
1182 break;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001183 case 'i':
1184 draw_ray_intersect = (draw_ray_intersect + 1) % 3;
1185 redraw();
1186 break;
1187 case 'l':
1188 var test = tests[testIndex];
1189 console.log("<div id=\"" + testTitles[testIndex] + "\" >");
1190 for (var curves in test) {
1191 var c = test[curves];
1192 var s = "{{";
1193 for (var i = 0; i < c.length; i += 2) {
1194 s += "{";
1195 s += c[i] + "," + c[i + 1];
1196 s += "}";
1197 if (i + 2 < c.length) {
1198 s += ", ";
1199 }
1200 }
1201 console.log(s + "}},");
1202 }
1203 console.log("</div>");
1204 break;
1205 case 'm':
1206 draw_midpoint = (draw_midpoint + 1) % 3;
1207 redraw();
1208 break;
1209 case 'N':
1210 testIndex += 9;
1211 case 'n':
1212 testIndex = (testIndex + 1) % tests.length;
1213 drawTop();
1214 break;
1215 case 'o':
1216 draw_order ^= true;
1217 redraw();
1218 break;
1219 case 'P':
1220 testIndex -= 9;
1221 case 'p':
1222 if (--testIndex < 0)
1223 testIndex = tests.length - 1;
1224 drawTop();
1225 break;
1226 case 'q':
1227 draw_quarterpoint = (draw_quarterpoint + 1) % 3;
1228 redraw();
1229 break;
1230 case 'r':
1231 for (var i = 0; i < testDivs.length; ++i) {
1232 var title = testDivs[i].id.toString();
1233 if (title == testTitles[testIndex]) {
1234 var str = testDivs[i].firstChild.data;
1235 parse(str, title);
1236 var original = tests.pop();
1237 testTitles.pop();
1238 tests[testIndex] = original;
1239 break;
1240 }
1241 }
1242 redraw();
1243 break;
1244 case 's':
1245 draw_sortpoint = (draw_sortpoint + 1) % 3;
1246 redraw();
1247 break;
1248 case 't':
1249 draw_t ^= true;
1250 redraw();
1251 break;
1252 case 'u':
1253 draw_closest_t ^= true;
1254 redraw();
1255 break;
1256 case 'v':
1257 draw_tangents = (draw_tangents + 1) % 4;
1258 redraw();
1259 break;
caryclark1049f122015-04-20 08:31:59 -07001260 case 'w':
1261 ++curveW;
1262 var choice = 0;
1263 draw_w = false;
1264 for (var curves in tests[testIndex]) {
1265 var curve = tests[testIndex][curves];
1266 if (curve.length != 7) {
1267 continue;
1268 }
1269 if (choice == curveW) {
1270 draw_w = true;
1271 break;
1272 }
1273 ++choice;
1274 }
1275 if (!draw_w) {
1276 curveW = -1;
1277 }
1278 redraw();
1279 break;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001280 case 'x':
1281 draw_point_xy ^= true;
1282 redraw();
1283 break;
1284 case 'y':
1285 draw_mouse_xy ^= true;
1286 redraw();
1287 break;
1288 case '\\':
1289 retina_scale ^= true;
1290 drawTop();
1291 break;
caryclark54359292015-03-26 07:52:43 -07001292 case '`':
1293 ++focus_on_selection;
1294 if (focus_on_selection >= tests[testIndex].length) {
1295 focus_on_selection = 0;
1296 }
1297 setScale(xmin, xmax, ymin, ymax);
1298 redraw();
1299 break;
1300 case '.':
caryclark1049f122015-04-20 08:31:59 -07001301 draw_id = (draw_id + 1) % 3;
caryclark54359292015-03-26 07:52:43 -07001302 redraw();
1303 break;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001304 }
1305 }
1306
1307 function doKeyDown(evt) {
1308 var char = evt.keyCode;
1309 var preventDefault = false;
1310 switch (char) {
1311 case 37: // left arrow
1312 if (evt.shiftKey) {
1313 testIndex -= 9;
1314 }
1315 if (--testIndex < 0)
1316 testIndex = tests.length - 1;
caryclark54359292015-03-26 07:52:43 -07001317 if (evt.ctrlKey) {
1318 redraw();
1319 } else {
1320 drawTop();
1321 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001322 preventDefault = true;
1323 break;
1324 case 39: // right arrow
1325 if (evt.shiftKey) {
1326 testIndex += 9;
1327 }
1328 if (++testIndex >= tests.length)
1329 testIndex = 0;
caryclark54359292015-03-26 07:52:43 -07001330 if (evt.ctrlKey) {
1331 redraw();
1332 } else {
1333 drawTop();
1334 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001335 preventDefault = true;
1336 break;
1337 }
1338 if (preventDefault) {
1339 evt.preventDefault();
1340 return false;
1341 }
1342 return true;
1343 }
1344
1345 function calcXY() {
1346 var e = window.event;
1347 var tgt = e.target || e.srcElement;
1348 var left = tgt.offsetLeft;
1349 var top = tgt.offsetTop;
Cary Clark219b4e82017-05-26 11:36:49 -04001350 mouseX = (e.clientX - left) / hscale + srcLeft;
1351 mouseY = (e.clientY - top) / vscale + srcTop;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001352 }
1353
1354 function calcLeftTop() {
Cary Clark219b4e82017-05-26 11:36:49 -04001355 srcLeft = mouseX - screenWidth / 2 / hscale;
1356 srcTop = mouseY - screenHeight / 2 / vscale;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001357 }
1358
1359 function handleMouseClick() {
caryclark1049f122015-04-20 08:31:59 -07001360 if ((!draw_t || !ptInTControl()) && (!draw_w || !ptInWControl())) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001361 calcXY();
1362 } else {
1363 redraw();
1364 }
1365 }
1366
1367 function initDown() {
1368 var test = tests[testIndex];
1369 var bestDistance = 1000000;
1370 activePt = -1;
1371 for (var curves in test) {
1372 var testCurve = test[curves];
caryclark1049f122015-04-20 08:31:59 -07001373 if (testCurve.length != 4 && (testCurve.length < 6 || testCurve.length > 8)) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001374 continue;
1375 }
caryclark1049f122015-04-20 08:31:59 -07001376 var testMax = testCurve.length == 7 ? 6 : testCurve.length;
1377 for (var i = 0; i < testMax; i += 2) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001378 var testX = testCurve[i];
1379 var testY = testCurve[i + 1];
1380 var dx = testX - mouseX;
1381 var dy = testY - mouseY;
1382 var dist = dx * dx + dy * dy;
1383 if (dist > bestDistance) {
1384 continue;
1385 }
1386 activeCurve = testCurve;
1387 activePt = i;
1388 bestDistance = dist;
1389 }
1390 }
1391 if (activePt >= 0) {
1392 lastX = mouseX;
1393 lastY = mouseY;
1394 }
1395 }
1396
1397 function handleMouseOver() {
1398 calcXY();
1399 if (draw_mouse_xy) {
1400 var num = mouseX.toFixed(decimal_places) + ", " + mouseY.toFixed(decimal_places);
1401 ctx.beginPath();
1402 ctx.rect(300, 100, num.length * 6, 10);
1403 ctx.fillStyle = "white";
1404 ctx.fill();
1405 ctx.font = "normal 10px Arial";
1406 ctx.fillStyle = "black";
1407 ctx.textAlign = "left";
1408 ctx.fillText(num, 300, 108);
1409 }
1410 if (!mouseDown) {
1411 activePt = -1;
1412 return;
1413 }
1414 if (activePt < 0) {
1415 initDown();
1416 return;
1417 }
1418 var deltaX = mouseX - lastX;
1419 var deltaY = mouseY - lastY;
1420 lastX = mouseX;
1421 lastY = mouseY;
1422 if (activePt == 0) {
1423 var test = tests[testIndex];
1424 for (var curves in test) {
1425 var testCurve = test[curves];
1426 testCurve[0] += deltaX;
1427 testCurve[1] += deltaY;
1428 }
1429 } else {
1430 activeCurve[activePt] += deltaX;
1431 activeCurve[activePt + 1] += deltaY;
1432 }
1433 redraw();
1434 }
1435
1436 function start() {
1437 for (var i = 0; i < testDivs.length; ++i) {
1438 var title = testDivs[i].id.toString();
1439 var str = testDivs[i].firstChild.data;
1440 parse(str, title);
1441 }
1442 drawTop();
1443 window.addEventListener('keypress', doKeyPress, true);
1444 window.addEventListener('keydown', doKeyDown, true);
1445 window.onresize = function () {
1446 drawTop();
1447 }
1448 }
1449
1450</script>
1451</head>
1452
1453<body onLoad="start();">
1454
1455<canvas id="canvas" width="750" height="500"
1456 onmousedown="mouseDown = true"
1457 onmouseup="mouseDown = false"
1458 onmousemove="handleMouseOver()"
1459 onclick="handleMouseClick()"
1460 ></canvas >
1461</body>
1462</html>