blob: 3c24aeb138e7a10c6a1adca2eadca009ef8f8234 [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
caryclarkef784fb2015-10-30 12:03:06 -07009<div id="sect0">
10{{{{306.588013,-227.983994}, {212.464996,-262.242004}, {95.5512009,58.9763985}}}, 0.707107008f},
11{{{{377.218994,-141.981003}, {40.578701,-201.339996}, {23.1854992,-102.697998}}}, 0.707107008f},
caryclarked0935a2015-10-22 07:23:52 -070012</div>
13
caryclarkef784fb2015-10-30 12:03:06 -070014<div id="sect1">
15{{{{377.218994,-141.981003}, {40.578701,-201.339996}, {23.1854992,-102.697998}}}, 0.707107008f},
16{{{{306.58801299999999, -227.983994}, {212.46499600000001, -262.24200400000001}, {95.551200899999998, 58.976398500000002}}}, 0.707107008f} id=1
17{{{{377.21899400000001, -141.98100299999999}, {237.77799285476553, -166.56830755921084}, {134.08399674208422, -155.06258330544892}}}, 0.788580656f} id=2
18{{{{134.08399674208422, -155.06258330544892}, {30.390000629402859, -143.55685905168704}, {23.185499199999999, -102.697998}}}, 0.923879623f} id=4
caryclarked0935a2015-10-22 07:23:52 -070019</div>
20
caryclarkef784fb2015-10-30 12:03:06 -070021<div id="sect2">
22{{{{306.588013,-227.983994}, {212.464996,-262.242004}, {95.5512009,58.9763985}}}, 0.707107008f},
23{{{{377.218994,-141.981003}, {40.578701,-201.339996}, {23.1854992,-102.697998}}}, 0.707107008f},
24{{{{205.78973252799028, -158.12538713371103}, {143.97848953841861, -74.076645245042371}, {95.551200899999998, 58.976398500000002}}}, 0.923879623f} id=3
25{{{{377.21899400000001, -141.98100299999999}, {237.77799285476553, -166.56830755921084}, {134.08399674208422, -155.06258330544892}}}, 0.788580656f} id=2
26</div>
27
28<div id="sect3">
29{{{{306.588013,-227.983994}, {212.464996,-262.242004}, {95.5512009,58.9763985}}}, 0.707107008f},
30{{{{377.218994,-141.981003}, {40.578701,-201.339996}, {23.1854992,-102.697998}}}, 0.707107008f},
31{{{{205.78973252799028, -158.12538713371103}, {143.97848953841861, -74.076645245042371}, {95.551200899999998, 58.976398500000002}}}, 0.923879623f} id=3
32{{{{252.08225670812539, -156.90491625851064}, {185.93099479842493, -160.81544543232982}, {134.08399674208422, -155.06258330544892}}}, 0.835816324f} id=6
33</div>
34
35<div id="sect4">
36{{{{306.588013,-227.983994}, {212.464996,-262.242004}, {95.5512009,58.9763985}}}, 0.707107008f},
37{{{{377.218994,-141.981003}, {40.578701,-201.339996}, {23.1854992,-102.697998}}}, 0.707107008f},
38{{{{205.78973252799028, -158.12538713371103}, {174.88411103320448, -116.10101618937664}, {145.19509369736275, -56.857102571363754}}}, 0.871667147f} id=3
39{{{{252.08225670812539, -156.90491625851064}, {185.93099479842493, -160.81544543232982}, {134.08399674208422, -155.06258330544892}}}, 0.835816324f} id=6
40</div>
41
42<div id="sect5">
43{{{{306.588013,-227.983994}, {212.464996,-262.242004}, {95.5512009,58.9763985}}}, 0.707107008f},
44{{{{377.218994,-141.981003}, {40.578701,-201.339996}, {23.1854992,-102.697998}}}, 0.707107008f},
45{{{{205.78973252799028, -158.12538713371103}, {174.88411103320448, -116.10101618937664}, {145.19509369736275, -56.857102571363754}}}, 0.871667147f} id=3
46{{{{252.08225670812539, -156.90491625851064}, {219.70109133058406, -158.81912754088933}, {190.17095392508796, -158.38373974664466}}}, 0.858306944f} id=6
47</div>
48
49<div id="sect6">
50{{{{306.588013,-227.983994}, {212.464996,-262.242004}, {95.5512009,58.9763985}}}, 0.707107008f},
51{{{{377.218994,-141.981003}, {40.578701,-201.339996}, {23.1854992,-102.697998}}}, 0.707107008f},
52{{{{205.78973252799028, -158.12538713371103}, {190.33692178059735, -137.11320166154385}, {174.87004877564593, -111.2132534799228}}}, 0.858117759f} id=3
53{{{{252.08225670812539, -156.90491625851064}, {219.70109133058406, -158.81912754088933}, {190.17095392508796, -158.38373974664466}}}, 0.858306944f} id=6
caryclark65f55312014-11-13 06:58:52 -080054</div>
55
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000056</div>
57
58<script type="text/javascript">
59
caryclark54359292015-03-26 07:52:43 -070060var testDivs = [
caryclarkef784fb2015-10-30 12:03:06 -070061sect0,
62sect1,
63sect2,
64sect3,
65sect4,
66sect5,
67sect6,
caryclark1049f122015-04-20 08:31:59 -070068];
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000069
caryclark54359292015-03-26 07:52:43 -070070 var decimal_places = 3;
71
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000072 var tests = [];
73 var testTitles = [];
74 var testIndex = 0;
75 var ctx;
caryclark54359292015-03-26 07:52:43 -070076
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000077 var subscale = 1;
78 var xmin, xmax, ymin, ymax;
79 var scale;
80 var initScale;
81 var mouseX, mouseY;
82 var mouseDown = false;
83 var srcLeft, srcTop;
84 var screenWidth, screenHeight;
85 var drawnPts;
86 var curveT = 0;
caryclark1049f122015-04-20 08:31:59 -070087 var curveW = -1;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000088
89 var lastX, lastY;
90 var activeCurve = [];
91 var activePt;
caryclark54359292015-03-26 07:52:43 -070092 var ids = [];
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000093
caryclark54359292015-03-26 07:52:43 -070094 var focus_on_selection = 0;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000095 var draw_t = false;
caryclark1049f122015-04-20 08:31:59 -070096 var draw_w = false;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000097 var draw_closest_t = false;
caryclarkfa6d6562014-07-29 12:13:28 -070098 var draw_cubic_red = false;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000099 var draw_derivative = false;
caryclark54359292015-03-26 07:52:43 -0700100 var draw_endpoints = 2;
caryclark1049f122015-04-20 08:31:59 -0700101 var draw_id = 0;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000102 var draw_midpoint = 0;
103 var draw_mouse_xy = false;
104 var draw_order = false;
105 var draw_point_xy = false;
106 var draw_ray_intersect = false;
107 var draw_quarterpoint = 0;
108 var draw_tangents = 1;
109 var draw_sortpoint = 0;
110 var retina_scale = !!window.devicePixelRatio;
111
112 function parse(test, title) {
113 var curveStrs = test.split("{{");
114 var pattern = /-?\d+\.*\d*e?-?\d*/g;
115 var curves = [];
116 for (var c in curveStrs) {
117 var curveStr = curveStrs[c];
caryclark54359292015-03-26 07:52:43 -0700118 var idPart = curveStr.split("id=");
119 var id = -1;
120 if (idPart.length == 2) {
121 id = parseInt(idPart[1]);
122 curveStr = idPart[0];
123 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000124 var points = curveStr.match(pattern);
125 var pts = [];
126 for (var wd in points) {
127 var num = parseFloat(points[wd]);
128 if (isNaN(num)) continue;
129 pts.push(num);
130 }
caryclark54359292015-03-26 07:52:43 -0700131 if (pts.length > 2) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000132 curves.push(pts);
caryclark54359292015-03-26 07:52:43 -0700133 }
134 if (id >= 0) {
135 ids.push(id);
136 ids.push(pts);
137 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000138 }
139 if (curves.length >= 1) {
140 tests.push(curves);
141 testTitles.push(title);
142 }
143 }
144
145 function init(test) {
146 var canvas = document.getElementById('canvas');
147 if (!canvas.getContext) return;
148 ctx = canvas.getContext('2d');
149 var resScale = retina_scale && window.devicePixelRatio ? window.devicePixelRatio : 1;
150 var unscaledWidth = window.innerWidth - 20;
151 var unscaledHeight = window.innerHeight - 20;
152 screenWidth = unscaledWidth;
153 screenHeight = unscaledHeight;
154 canvas.width = unscaledWidth * resScale;
155 canvas.height = unscaledHeight * resScale;
156 canvas.style.width = unscaledWidth + 'px';
157 canvas.style.height = unscaledHeight + 'px';
158 if (resScale != 1) {
159 ctx.scale(resScale, resScale);
160 }
161 xmin = Infinity;
162 xmax = -Infinity;
163 ymin = Infinity;
164 ymax = -Infinity;
165 for (var curves in test) {
166 var curve = test[curves];
caryclark1049f122015-04-20 08:31:59 -0700167 var last = curve.length - (curve.length % 2 == 1 ? 1 : 0);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000168 for (var idx = 0; idx < last; idx += 2) {
169 xmin = Math.min(xmin, curve[idx]);
170 xmax = Math.max(xmax, curve[idx]);
171 ymin = Math.min(ymin, curve[idx + 1]);
172 ymax = Math.max(ymax, curve[idx + 1]);
173 }
174 }
caryclark54359292015-03-26 07:52:43 -0700175 xmin -= Math.min(1, Math.max(xmax - xmin, ymax - ymin));
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000176 var testW = xmax - xmin;
177 var testH = ymax - ymin;
178 subscale = 1;
179 while (testW * subscale < 0.1 && testH * subscale < 0.1) {
180 subscale *= 10;
181 }
182 while (testW * subscale > 10 && testH * subscale > 10) {
183 subscale /= 10;
184 }
185 setScale(xmin, xmax, ymin, ymax);
186 mouseX = (screenWidth / 2) / scale + srcLeft;
187 mouseY = (screenHeight / 2) / scale + srcTop;
188 initScale = scale;
189 }
190
191 function setScale(x0, x1, y0, y1) {
192 var srcWidth = x1 - x0;
193 var srcHeight = y1 - y0;
194 var usableWidth = screenWidth;
195 var xDigits = Math.ceil(Math.log(Math.abs(xmax)) / Math.log(10));
196 var yDigits = Math.ceil(Math.log(Math.abs(ymax)) / Math.log(10));
197 usableWidth -= (xDigits + yDigits) * 10;
198 usableWidth -= decimal_places * 10;
199 var hscale = usableWidth / srcWidth;
200 var vscale = screenHeight / srcHeight;
201 scale = Math.min(hscale, vscale);
202 var invScale = 1 / scale;
203 var sxmin = x0 - invScale * 5;
204 var symin = y0 - invScale * 10;
205 var sxmax = x1 + invScale * (6 * decimal_places + 10);
206 var symax = y1 + invScale * 10;
207 srcWidth = sxmax - sxmin;
208 srcHeight = symax - symin;
209 hscale = usableWidth / srcWidth;
210 vscale = screenHeight / srcHeight;
211 scale = Math.min(hscale, vscale);
212 srcLeft = sxmin;
213 srcTop = symin;
214 }
215
216function dxy_at_t(curve, t) {
217 var dxy = {};
218 if (curve.length == 6) {
219 var a = t - 1;
220 var b = 1 - 2 * t;
221 var c = t;
222 dxy.x = a * curve[0] + b * curve[2] + c * curve[4];
223 dxy.y = a * curve[1] + b * curve[3] + c * curve[5];
caryclark1049f122015-04-20 08:31:59 -0700224 } else if (curve.length == 7) {
225 var p20x = curve[4] - curve[0];
226 var p20y = curve[5] - curve[1];
227 var p10xw = (curve[2] - curve[0]) * curve[6];
228 var p10yw = (curve[3] - curve[1]) * curve[6];
229 var coeff0x = curve[6] * p20x - p20x;
230 var coeff0y = curve[6] * p20y - p20y;
231 var coeff1x = p20x - 2 * p10xw;
232 var coeff1y = p20y - 2 * p10yw;
233 dxy.x = t * (t * coeff0x + coeff1x) + p10xw;
234 dxy.y = t * (t * coeff0y + coeff1y) + p10yw;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000235 } else if (curve.length == 8) {
236 var one_t = 1 - t;
237 var a = curve[0];
238 var b = curve[2];
239 var c = curve[4];
240 var d = curve[6];
241 dxy.x = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
242 a = curve[1];
243 b = curve[3];
244 c = curve[5];
245 d = curve[7];
246 dxy.y = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
247 }
248 return dxy;
249}
250
251 var flt_epsilon = 1.19209290E-07;
252
253 function approximately_zero(A) {
254 return Math.abs(A) < flt_epsilon;
255 }
256
257 function approximately_zero_inverse(A) {
258 return Math.abs(A) > (1 / flt_epsilon);
259 }
260
261 function quad_real_roots(A, B, C) {
262 var s = [];
263 var p = B / (2 * A);
264 var q = C / A;
265 if (approximately_zero(A) && (approximately_zero_inverse(p)
266 || approximately_zero_inverse(q))) {
267 if (approximately_zero(B)) {
268 if (C == 0) {
269 s[0] = 0;
270 }
271 return s;
272 }
273 s[0] = -C / B;
274 return s;
275 }
276 /* normal form: x^2 + px + q = 0 */
277 var p2 = p * p;
278 if (!approximately_zero(p2 - q) && p2 < q) {
279 return s;
280 }
281 var sqrt_D = 0;
282 if (p2 > q) {
283 sqrt_D = Math.sqrt(p2 - q);
284 }
285 s[0] = sqrt_D - p;
286 var flip = -sqrt_D - p;
287 if (!approximately_zero(s[0] - flip)) {
288 s[1] = flip;
289 }
290 return s;
291 }
292
293 function cubic_real_roots(A, B, C, D) {
294 if (approximately_zero(A)) { // we're just a quadratic
295 return quad_real_roots(B, C, D);
296 }
297 if (approximately_zero(D)) { // 0 is one root
298 var s = quad_real_roots(A, B, C);
299 for (var i = 0; i < s.length; ++i) {
300 if (approximately_zero(s[i])) {
301 return s;
302 }
303 }
304 s.push(0);
305 return s;
306 }
307 if (approximately_zero(A + B + C + D)) { // 1 is one root
308 var s = quad_real_roots(A, A + B, -D);
309 for (var i = 0; i < s.length; ++i) {
310 if (approximately_zero(s[i] - 1)) {
311 return s;
312 }
313 }
314 s.push(1);
315 return s;
316 }
317 var a, b, c;
318 var invA = 1 / A;
319 a = B * invA;
320 b = C * invA;
321 c = D * invA;
322 var a2 = a * a;
323 var Q = (a2 - b * 3) / 9;
324 var R = (2 * a2 * a - 9 * a * b + 27 * c) / 54;
325 var R2 = R * R;
326 var Q3 = Q * Q * Q;
327 var R2MinusQ3 = R2 - Q3;
328 var adiv3 = a / 3;
329 var r;
330 var roots = [];
331 if (R2MinusQ3 < 0) { // we have 3 real roots
332 var theta = Math.acos(R / Math.sqrt(Q3));
333 var neg2RootQ = -2 * Math.sqrt(Q);
334 r = neg2RootQ * Math.cos(theta / 3) - adiv3;
335 roots.push(r);
336 r = neg2RootQ * Math.cos((theta + 2 * Math.PI) / 3) - adiv3;
337 if (!approximately_zero(roots[0] - r)) {
338 roots.push(r);
339 }
340 r = neg2RootQ * Math.cos((theta - 2 * Math.PI) / 3) - adiv3;
341 if (!approximately_zero(roots[0] - r) && (roots.length == 1
342 || !approximately_zero(roots[1] - r))) {
343 roots.push(r);
344 }
345 } else { // we have 1 real root
346 var sqrtR2MinusQ3 = Math.sqrt(R2MinusQ3);
347 var A = Math.abs(R) + sqrtR2MinusQ3;
348 A = Math.pow(A, 1/3);
349 if (R > 0) {
350 A = -A;
351 }
352 if (A != 0) {
353 A += Q / A;
354 }
355 r = A - adiv3;
356 roots.push(r);
357 if (approximately_zero(R2 - Q3)) {
358 r = -A / 2 - adiv3;
caryclark1049f122015-04-20 08:31:59 -0700359 if (!approximately_zero(roots[0] - r)) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000360 roots.push(r);
361 }
362 }
363 }
364 return roots;
365 }
366
367 function approximately_zero_or_more(tValue) {
368 return tValue >= -flt_epsilon;
369 }
370
371 function approximately_one_or_less(tValue) {
372 return tValue <= 1 + flt_epsilon;
373 }
374
375 function approximately_less_than_zero(tValue) {
376 return tValue < flt_epsilon;
377 }
378
379 function approximately_greater_than_one(tValue) {
380 return tValue > 1 - flt_epsilon;
381 }
382
383 function add_valid_ts(s) {
384 var t = [];
385 nextRoot:
386 for (var index = 0; index < s.length; ++index) {
387 var tValue = s[index];
388 if (approximately_zero_or_more(tValue) && approximately_one_or_less(tValue)) {
389 if (approximately_less_than_zero(tValue)) {
390 tValue = 0;
391 } else if (approximately_greater_than_one(tValue)) {
392 tValue = 1;
393 }
394 for (var idx2 = 0; idx2 < t.length; ++idx2) {
395 if (approximately_zero(t[idx2] - tValue)) {
396 continue nextRoot;
397 }
398 }
399 t.push(tValue);
400 }
401 }
402 return t;
403 }
404
405 function quad_roots(A, B, C) {
406 var s = quad_real_roots(A, B, C);
407 var foundRoots = add_valid_ts(s);
408 return foundRoots;
409 }
410
411 function cubic_roots(A, B, C, D) {
412 var s = cubic_real_roots(A, B, C, D);
413 var foundRoots = add_valid_ts(s);
414 return foundRoots;
415 }
416
417 function ray_curve_intersect(startPt, endPt, curve) {
418 var adj = endPt[0] - startPt[0];
419 var opp = endPt[1] - startPt[1];
420 var r = [];
caryclark1049f122015-04-20 08:31:59 -0700421 var len = (curve.length == 7 ? 6 : curve.length) / 2;
422 for (var n = 0; n < len; ++n) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000423 r[n] = (curve[n * 2 + 1] - startPt[1]) * adj - (curve[n * 2] - startPt[0]) * opp;
424 }
425 if (curve.length == 6) {
426 var A = r[2];
427 var B = r[1];
428 var C = r[0];
429 A += C - 2 * B; // A = a - 2*b + c
430 B -= C; // B = -(b - c)
431 return quad_roots(A, 2 * B, C);
432 }
caryclark1049f122015-04-20 08:31:59 -0700433 if (curve.length == 7) {
434 var A = r[2];
435 var B = r[1] * curve[6];
436 var C = r[0];
437 A += C - 2 * B; // A = a - 2*b + c
438 B -= C; // B = -(b - c)
439 return quad_roots(A, 2 * B, C);
440 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000441 var A = r[3]; // d
442 var B = r[2] * 3; // 3*c
443 var C = r[1] * 3; // 3*b
444 var D = r[0]; // a
445 A -= D - C + B; // A = -a + 3*b - 3*c + d
446 B += 3 * D - 2 * C; // B = 3*a - 6*b + 3*c
447 C -= 3 * D; // C = -3*a + 3*b
448 return cubic_roots(A, B, C, D);
449 }
450
451 function x_at_t(curve, t) {
452 var one_t = 1 - t;
453 if (curve.length == 4) {
454 return one_t * curve[0] + t * curve[2];
455 }
456 var one_t2 = one_t * one_t;
457 var t2 = t * t;
458 if (curve.length == 6) {
459 return one_t2 * curve[0] + 2 * one_t * t * curve[2] + t2 * curve[4];
460 }
caryclark1049f122015-04-20 08:31:59 -0700461 if (curve.length == 7) {
462 var numer = one_t2 * curve[0] + 2 * one_t * t * curve[2] * curve[6]
463 + t2 * curve[4];
464 var denom = one_t2 + 2 * one_t * t * curve[6]
465 + t2;
466 return numer / denom;
467 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000468 var a = one_t2 * one_t;
469 var b = 3 * one_t2 * t;
470 var c = 3 * one_t * t2;
471 var d = t2 * t;
472 return a * curve[0] + b * curve[2] + c * curve[4] + d * curve[6];
473 }
474
475 function y_at_t(curve, t) {
476 var one_t = 1 - t;
477 if (curve.length == 4) {
478 return one_t * curve[1] + t * curve[3];
479 }
480 var one_t2 = one_t * one_t;
481 var t2 = t * t;
482 if (curve.length == 6) {
483 return one_t2 * curve[1] + 2 * one_t * t * curve[3] + t2 * curve[5];
484 }
caryclark1049f122015-04-20 08:31:59 -0700485 if (curve.length == 7) {
486 var numer = one_t2 * curve[1] + 2 * one_t * t * curve[3] * curve[6]
487 + t2 * curve[5];
488 var denom = one_t2 + 2 * one_t * t * curve[6]
489 + t2;
490 return numer / denom;
491 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000492 var a = one_t2 * one_t;
493 var b = 3 * one_t2 * t;
494 var c = 3 * one_t * t2;
495 var d = t2 * t;
496 return a * curve[1] + b * curve[3] + c * curve[5] + d * curve[7];
497 }
498
499 function drawPointAtT(curve) {
500 var x = x_at_t(curve, curveT);
501 var y = y_at_t(curve, curveT);
502 drawPoint(x, y);
503 }
504
505 function drawLine(x1, y1, x2, y2) {
506 ctx.beginPath();
507 ctx.moveTo((x1 - srcLeft) * scale,
508 (y1 - srcTop) * scale);
509 ctx.lineTo((x2 - srcLeft) * scale,
510 (y2 - srcTop) * scale);
511 ctx.stroke();
512 }
513
514 function drawPoint(px, py) {
515 for (var pts = 0; pts < drawnPts.length; pts += 2) {
516 var x = drawnPts[pts];
517 var y = drawnPts[pts + 1];
518 if (px == x && py == y) {
519 return;
520 }
521 }
522 drawnPts.push(px);
523 drawnPts.push(py);
524 var _px = (px - srcLeft) * scale;
525 var _py = (py - srcTop) * scale;
526 ctx.beginPath();
527 ctx.arc(_px, _py, 3, 0, Math.PI * 2, true);
528 ctx.closePath();
529 ctx.stroke();
530 if (draw_point_xy) {
531 var label = px.toFixed(decimal_places) + ", " + py.toFixed(decimal_places);
532 ctx.font = "normal 10px Arial";
533 ctx.textAlign = "left";
534 ctx.fillStyle = "black";
535 ctx.fillText(label, _px + 5, _py);
536 }
537 }
538
539 function drawPointSolid(px, py) {
540 drawPoint(px, py);
541 ctx.fillStyle = "rgba(0,0,0, 0.4)";
542 ctx.fill();
543 }
544
545 function crossPt(origin, pt1, pt2) {
546 return ((pt1[0] - origin[0]) * (pt2[1] - origin[1])
547 - (pt1[1] - origin[1]) * (pt2[0] - origin[0])) > 0 ? 0 : 1;
548 }
549
550 // may not work well for cubics
551 function curveClosestT(curve, x, y) {
552 var closest = -1;
553 var closestDist = Infinity;
554 var l = Infinity, t = Infinity, r = -Infinity, b = -Infinity;
555 for (var i = 0; i < 16; ++i) {
556 var testX = x_at_t(curve, i / 16);
557 l = Math.min(testX, l);
558 r = Math.max(testX, r);
559 var testY = y_at_t(curve, i / 16);
560 t = Math.min(testY, t);
561 b = Math.max(testY, b);
562 var dx = testX - x;
563 var dy = testY - y;
564 var dist = dx * dx + dy * dy;
565 if (closestDist > dist) {
566 closestDist = dist;
567 closest = i;
568 }
569 }
570 var boundsX = r - l;
571 var boundsY = b - t;
572 var boundsDist = boundsX * boundsX + boundsY * boundsY;
573 if (closestDist > boundsDist) {
574 return -1;
575 }
576 console.log("closestDist = " + closestDist + " boundsDist = " + boundsDist
577 + " t = " + closest / 16);
578 return closest / 16;
579 }
580
caryclark1049f122015-04-20 08:31:59 -0700581 var kMaxConicToQuadPOW2 = 5;
582
583 function computeQuadPOW2(curve, tol) {
584 var a = curve[6] - 1;
585 var k = a / (4 * (2 + a));
586 var x = k * (curve[0] - 2 * curve[2] + curve[4]);
587 var y = k * (curve[1] - 2 * curve[3] + curve[5]);
588
589 var error = Math.sqrt(x * x + y * y);
590 var pow2;
591 for (pow2 = 0; pow2 < kMaxConicToQuadPOW2; ++pow2) {
592 if (error <= tol) {
593 break;
594 }
595 error *= 0.25;
596 }
597 return pow2;
598 }
599
600 function subdivide_w_value(w) {
601 return Math.sqrt(0.5 + w * 0.5);
602 }
603
604 function chop(curve, part1, part2) {
605 var w = curve[6];
606 var scale = 1 / (1 + w);
607 part1[0] = curve[0];
608 part1[1] = curve[1];
609 part1[2] = (curve[0] + curve[2] * w) * scale;
610 part1[3] = (curve[1] + curve[3] * w) * scale;
611 part1[4] = part2[0] = (curve[0] + (curve[2] * w) * 2 + curve[4]) * scale * 0.5;
612 part1[5] = part2[1] = (curve[1] + (curve[3] * w) * 2 + curve[5]) * scale * 0.5;
613 part2[2] = (curve[2] * w + curve[4]) * scale;
614 part2[3] = (curve[3] * w + curve[5]) * scale;
615 part2[4] = curve[4];
616 part2[5] = curve[5];
617 part1[6] = part2[6] = subdivide_w_value(w);
618 }
619
620 function subdivide(curve, level, pts) {
621 if (0 == level) {
622 pts.push(curve[2]);
623 pts.push(curve[3]);
624 pts.push(curve[4]);
625 pts.push(curve[5]);
626 } else {
627 var part1 = [], part2 = [];
628 chop(curve, part1, part2);
629 --level;
630 subdivide(part1, level, pts);
631 subdivide(part2, level, pts);
632 }
633 }
634
635 function chopIntoQuadsPOW2(curve, pow2, pts) {
636 subdivide(curve, pow2, pts);
637 return 1 << pow2;
638 }
639
640 function drawConic(curve, srcLeft, srcTop, scale) {
641 var tol = 1 / scale;
642 var pow2 = computeQuadPOW2(curve, tol);
643 var pts = [];
644 chopIntoQuadsPOW2(curve, pow2, pts);
645 for (var i = 0; i < pts.length; i += 4) {
646 ctx.quadraticCurveTo(
647 (pts[i + 0] - srcLeft) * scale, (pts[i + 1] - srcTop) * scale,
648 (pts[i + 2] - srcLeft) * scale, (pts[i + 3] - srcTop) * scale);
649 }
650 }
651
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000652 function draw(test, title) {
653 ctx.font = "normal 50px Arial";
654 ctx.textAlign = "left";
655 ctx.fillStyle = "rgba(0,0,0, 0.1)";
656 ctx.fillText(title, 50, 50);
657 ctx.font = "normal 10px Arial";
658 // ctx.lineWidth = "1.001"; "0.999";
659 var hullStarts = [];
660 var hullEnds = [];
661 var midSpokes = [];
662 var midDist = [];
663 var origin = [];
664 var shortSpokes = [];
665 var shortDist = [];
666 var sweeps = [];
667 drawnPts = [];
668 for (var curves in test) {
669 var curve = test[curves];
670 origin.push(curve[0]);
671 origin.push(curve[1]);
672 var startPt = [];
673 startPt.push(curve[2]);
674 startPt.push(curve[3]);
675 hullStarts.push(startPt);
676 var endPt = [];
677 if (curve.length == 4) {
678 endPt.push(curve[2]);
679 endPt.push(curve[3]);
caryclark1049f122015-04-20 08:31:59 -0700680 } else if (curve.length == 6 || curve.length == 7) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000681 endPt.push(curve[4]);
682 endPt.push(curve[5]);
683 } else if (curve.length == 8) {
684 endPt.push(curve[6]);
685 endPt.push(curve[7]);
686 }
687 hullEnds.push(endPt);
688 var sweep = crossPt(origin, startPt, endPt);
689 sweeps.push(sweep);
690 var midPt = [];
691 midPt.push(x_at_t(curve, 0.5));
692 midPt.push(y_at_t(curve, 0.5));
693 midSpokes.push(midPt);
694 var shortPt = [];
695 shortPt.push(x_at_t(curve, 0.25));
696 shortPt.push(y_at_t(curve, 0.25));
697 shortSpokes.push(shortPt);
698 var dx = midPt[0] - origin[0];
699 var dy = midPt[1] - origin[1];
700 var dist = Math.sqrt(dx * dx + dy * dy);
701 midDist.push(dist);
702 dx = shortPt[0] - origin[0];
703 dy = shortPt[1] - origin[1];
704 dist = Math.sqrt(dx * dx + dy * dy);
705 shortDist.push(dist);
706 }
707 var intersect = [];
708 var useIntersect = false;
709 var maxWidth = Math.max(xmax - xmin, ymax - ymin);
710 for (var curves in test) {
711 var curve = test[curves];
caryclark1049f122015-04-20 08:31:59 -0700712 if (curve.length >= 6 && curve.length <= 8) {
commit-bot@chromium.org8cb1daa2014-04-25 12:59:11 +0000713 var opp = curves == 0 || curves == 1 ? 0 : 1;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000714 var sects = ray_curve_intersect(origin, hullEnds[opp], curve);
715 intersect.push(sects);
716 if (sects.length > 1) {
717 var intersection = sects[0];
718 if (intersection == 0) {
719 intersection = sects[1];
720 }
721 var ix = x_at_t(curve, intersection) - origin[0];
722 var iy = y_at_t(curve, intersection) - origin[1];
723 var ex = hullEnds[opp][0] - origin[0];
724 var ey = hullEnds[opp][1] - origin[1];
725 if (ix * ex >= 0 && iy * ey >= 0) {
726 var iDist = Math.sqrt(ix * ix + iy * iy);
727 var eDist = Math.sqrt(ex * ex + ey * ey);
728 var delta = Math.abs(iDist - eDist) / maxWidth;
caryclark1049f122015-04-20 08:31:59 -0700729 if (delta > (curve.length != 8 ? 1e-5 : 1e-4)) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000730 useIntersect ^= true;
731 }
732 }
733 }
734 }
735 }
commit-bot@chromium.org8cb1daa2014-04-25 12:59:11 +0000736 var midLeft = curves != 0 ? crossPt(origin, midSpokes[0], midSpokes[1]) : 0;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000737 var firstInside;
738 if (useIntersect) {
739 var sect1 = intersect[0].length > 1;
740 var sIndex = sect1 ? 0 : 1;
741 var sects = intersect[sIndex];
742 var intersection = sects[0];
743 if (intersection == 0) {
744 intersection = sects[1];
745 }
746 var curve = test[sIndex];
747 var ix = x_at_t(curve, intersection) - origin[0];
748 var iy = y_at_t(curve, intersection) - origin[1];
749 var opp = sect1 ? 1 : 0;
750 var ex = hullEnds[opp][0] - origin[0];
751 var ey = hullEnds[opp][1] - origin[1];
752 var iDist = ix * ix + iy * iy;
753 var eDist = ex * ex + ey * ey;
754 firstInside = (iDist > eDist) ^ (sIndex == 0) ^ sweeps[0];
755// console.log("iDist=" + iDist + " eDist=" + eDist + " sIndex=" + sIndex
756 // + " sweeps[0]=" + sweeps[0]);
757 } else {
758 // console.log("midLeft=" + midLeft);
759 firstInside = midLeft != 0;
760 }
761 var shorter = midDist[1] < midDist[0];
762 var shortLeft = shorter ? crossPt(origin, shortSpokes[0], midSpokes[1])
763 : crossPt(origin, midSpokes[0], shortSpokes[1]);
764 var startCross = crossPt(origin, hullStarts[0], hullStarts[1]);
765 var disallowShort = midLeft == startCross && midLeft == sweeps[0]
766 && midLeft == sweeps[1];
767
768 // console.log("midLeft=" + midLeft + " startCross=" + startCross);
769 var intersectIndex = 0;
770 for (var curves in test) {
caryclark1049f122015-04-20 08:31:59 -0700771 var curve = test[draw_id != 2 ? curves : test.length - curves - 1];
772 if (curve.length != 4 && curve.length != 6 && curve.length != 7 && curve.length != 8) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000773 continue;
774 }
775 ctx.lineWidth = 1;
776 if (draw_tangents != 0) {
caryclarkfa6d6562014-07-29 12:13:28 -0700777 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000778 ctx.strokeStyle = "rgba(255,0,0, 0.3)";
779 } else {
780 ctx.strokeStyle = "rgba(0,0,255, 0.3)";
781 }
782 drawLine(curve[0], curve[1], curve[2], curve[3]);
783 if (draw_tangents != 2) {
784 if (curve.length > 4) drawLine(curve[2], curve[3], curve[4], curve[5]);
caryclark1049f122015-04-20 08:31:59 -0700785 if (curve.length == 8) drawLine(curve[4], curve[5], curve[6], curve[7]);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000786 }
787 if (draw_tangents != 1) {
caryclark1049f122015-04-20 08:31:59 -0700788 if (curve.length == 6 || curve.length == 7) {
789 drawLine(curve[0], curve[1], curve[4], curve[5]);
790 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000791 if (curve.length == 8) drawLine(curve[0], curve[1], curve[6], curve[7]);
792 }
793 }
794 ctx.beginPath();
795 ctx.moveTo((curve[0] - srcLeft) * scale, (curve[1] - srcTop) * scale);
796 if (curve.length == 4) {
797 ctx.lineTo((curve[2] - srcLeft) * scale, (curve[3] - srcTop) * scale);
798 } else if (curve.length == 6) {
799 ctx.quadraticCurveTo(
800 (curve[2] - srcLeft) * scale, (curve[3] - srcTop) * scale,
801 (curve[4] - srcLeft) * scale, (curve[5] - srcTop) * scale);
caryclark1049f122015-04-20 08:31:59 -0700802 } else if (curve.length == 7) {
803 drawConic(curve, srcLeft, srcTop, scale);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000804 } else {
805 ctx.bezierCurveTo(
806 (curve[2] - srcLeft) * scale, (curve[3] - srcTop) * scale,
807 (curve[4] - srcLeft) * scale, (curve[5] - srcTop) * scale,
808 (curve[6] - srcLeft) * scale, (curve[7] - srcTop) * scale);
809 }
caryclarkfa6d6562014-07-29 12:13:28 -0700810 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000811 ctx.strokeStyle = "rgba(255,0,0, 1)";
812 } else {
813 ctx.strokeStyle = "rgba(0,0,255, 1)";
814 }
815 ctx.stroke();
caryclark54359292015-03-26 07:52:43 -0700816 if (draw_endpoints > 0) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000817 drawPoint(curve[0], curve[1]);
caryclark54359292015-03-26 07:52:43 -0700818 if (draw_endpoints > 1 || curve.length == 4) {
819 drawPoint(curve[2], curve[3]);
820 }
caryclark1049f122015-04-20 08:31:59 -0700821 if (curve.length == 6 || curve.length == 7 ||
822 (draw_endpoints > 1 && curve.length == 8)) {
caryclark54359292015-03-26 07:52:43 -0700823 drawPoint(curve[4], curve[5]);
824 }
825 if (curve.length == 8) drawPoint(curve[6], curve[7]);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000826 }
827 if (draw_midpoint != 0) {
828 if ((curves == 0) == (midLeft == 0)) {
829 ctx.strokeStyle = "rgba(0,180,127, 0.6)";
830 } else {
831 ctx.strokeStyle = "rgba(127,0,127, 0.6)";
832 }
833 var midX = x_at_t(curve, 0.5);
834 var midY = y_at_t(curve, 0.5);
835 drawPointSolid(midX, midY);
836 if (draw_midpoint > 1) {
837 drawLine(curve[0], curve[1], midX, midY);
838 }
839 }
840 if (draw_quarterpoint != 0) {
841 if ((curves == 0) == (shortLeft == 0)) {
842 ctx.strokeStyle = "rgba(0,191,63, 0.6)";
843 } else {
844 ctx.strokeStyle = "rgba(63,0,191, 0.6)";
845 }
846 var midT = (curves == 0) == shorter ? 0.25 : 0.5;
847 var midX = x_at_t(curve, midT);
848 var midY = y_at_t(curve, midT);
849 drawPointSolid(midX, midY);
850 if (draw_quarterpoint > 1) {
851 drawLine(curve[0], curve[1], midX, midY);
852 }
853 }
854 if (draw_sortpoint != 0) {
855 if ((curves == 0) == ((disallowShort == -1 ? midLeft : shortLeft) == 0)) {
856 ctx.strokeStyle = "rgba(0,155,37, 0.6)";
857 } else {
858 ctx.strokeStyle = "rgba(37,0,155, 0.6)";
859 }
860 var midT = (curves == 0) == shorter && disallowShort != curves ? 0.25 : 0.5;
861 console.log("curves=" + curves + " disallowShort=" + disallowShort
862 + " midLeft=" + midLeft + " shortLeft=" + shortLeft
863 + " shorter=" + shorter + " midT=" + midT);
864 var midX = x_at_t(curve, midT);
865 var midY = y_at_t(curve, midT);
866 drawPointSolid(midX, midY);
867 if (draw_sortpoint > 1) {
868 drawLine(curve[0], curve[1], midX, midY);
869 }
870 }
871 if (draw_ray_intersect != 0) {
872 ctx.strokeStyle = "rgba(75,45,199, 0.6)";
caryclark1049f122015-04-20 08:31:59 -0700873 if (curve.length >= 6 && curve.length <= 8) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000874 var intersections = intersect[intersectIndex];
875 for (var i in intersections) {
876 var intersection = intersections[i];
877 var x = x_at_t(curve, intersection);
878 var y = y_at_t(curve, intersection);
879 drawPointSolid(x, y);
880 if (draw_ray_intersect > 1) {
881 drawLine(curve[0], curve[1], x, y);
882 }
883 }
884 }
885 ++intersectIndex;
886 }
887 if (draw_order) {
888 var px = x_at_t(curve, 0.75);
889 var py = y_at_t(curve, 0.75);
890 var _px = (px - srcLeft) * scale;
891 var _py = (py - srcTop) * scale;
892 ctx.beginPath();
893 ctx.arc(_px, _py, 15, 0, Math.PI * 2, true);
894 ctx.closePath();
895 ctx.fillStyle = "white";
896 ctx.fill();
caryclarkfa6d6562014-07-29 12:13:28 -0700897 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000898 ctx.strokeStyle = "rgba(255,0,0, 1)";
899 ctx.fillStyle = "rgba(255,0,0, 1)";
900 } else {
901 ctx.strokeStyle = "rgba(0,0,255, 1)";
902 ctx.fillStyle = "rgba(0,0,255, 1)";
903 }
904 ctx.stroke();
905 ctx.font = "normal 16px Arial";
906 ctx.textAlign = "center";
907 ctx.fillText(parseInt(curves) + 1, _px, _py + 5);
908 }
909 if (draw_closest_t) {
910 var t = curveClosestT(curve, mouseX, mouseY);
911 if (t >= 0) {
912 var x = x_at_t(curve, t);
913 var y = y_at_t(curve, t);
914 drawPointSolid(x, y);
915 }
916 }
917 if (!approximately_zero(scale - initScale)) {
918 ctx.font = "normal 20px Arial";
919 ctx.fillStyle = "rgba(0,0,0, 0.3)";
920 ctx.textAlign = "right";
921 ctx.fillText(scale.toFixed(decimal_places) + 'x',
922 screenWidth - 10, screenHeight - 5);
923 }
924 if (draw_t) {
925 drawPointAtT(curve);
926 }
caryclark1049f122015-04-20 08:31:59 -0700927 if (draw_id != 0) {
caryclark54359292015-03-26 07:52:43 -0700928 var id = -1;
929 for (var i = 0; i < ids.length; i += 2) {
930 if (ids[i + 1] == curve) {
931 id = ids[i];
932 break;
933 }
934 }
935 if (id >= 0) {
936 var px = x_at_t(curve, 0.5);
937 var py = y_at_t(curve, 0.5);
938 var _px = (px - srcLeft) * scale;
939 var _py = (py - srcTop) * scale;
940 ctx.beginPath();
941 ctx.arc(_px, _py, 15, 0, Math.PI * 2, true);
942 ctx.closePath();
943 ctx.fillStyle = "white";
944 ctx.fill();
945 ctx.strokeStyle = "rgba(255,0,0, 1)";
946 ctx.fillStyle = "rgba(255,0,0, 1)";
947 ctx.stroke();
948 ctx.font = "normal 16px Arial";
949 ctx.textAlign = "center";
950 ctx.fillText(id, _px, _py + 5);
951 }
952 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000953 }
954 if (draw_t) {
955 drawCurveTControl();
956 }
caryclark1049f122015-04-20 08:31:59 -0700957 if (draw_w) {
958 drawCurveWControl();
959 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000960 }
961
962 function drawCurveTControl() {
963 ctx.lineWidth = 2;
964 ctx.strokeStyle = "rgba(0,0,0, 0.3)";
965 ctx.beginPath();
966 ctx.rect(screenWidth - 80, 40, 28, screenHeight - 80);
967 ctx.stroke();
968 var ty = 40 + curveT * (screenHeight - 80);
969 ctx.beginPath();
970 ctx.moveTo(screenWidth - 80, ty);
971 ctx.lineTo(screenWidth - 85, ty - 5);
972 ctx.lineTo(screenWidth - 85, ty + 5);
973 ctx.lineTo(screenWidth - 80, ty);
974 ctx.fillStyle = "rgba(0,0,0, 0.6)";
975 ctx.fill();
976 var num = curveT.toFixed(decimal_places);
977 ctx.font = "normal 10px Arial";
978 ctx.textAlign = "left";
979 ctx.fillText(num, screenWidth - 78, ty);
980 }
981
caryclark1049f122015-04-20 08:31:59 -0700982 function drawCurveWControl() {
983 var w = -1;
984 var choice = 0;
985 for (var curves in tests[testIndex]) {
986 var curve = tests[testIndex][curves];
987 if (curve.length != 7) {
988 continue;
989 }
990 if (choice == curveW) {
991 w = curve[6];
992 break;
993 }
994 ++choice;
995 }
996 if (w < 0) {
997 return;
998 }
999 ctx.lineWidth = 2;
1000 ctx.strokeStyle = "rgba(0,0,0, 0.3)";
1001 ctx.beginPath();
1002 ctx.rect(screenWidth - 40, 40, 28, screenHeight - 80);
1003 ctx.stroke();
1004 var ty = 40 + w * (screenHeight - 80);
1005 ctx.beginPath();
1006 ctx.moveTo(screenWidth - 40, ty);
1007 ctx.lineTo(screenWidth - 45, ty - 5);
1008 ctx.lineTo(screenWidth - 45, ty + 5);
1009 ctx.lineTo(screenWidth - 40, ty);
1010 ctx.fillStyle = "rgba(0,0,0, 0.6)";
1011 ctx.fill();
1012 var num = w.toFixed(decimal_places);
1013 ctx.font = "normal 10px Arial";
1014 ctx.textAlign = "left";
1015 ctx.fillText(num, screenWidth - 38, ty);
1016 }
1017
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001018 function ptInTControl() {
1019 var e = window.event;
1020 var tgt = e.target || e.srcElement;
1021 var left = tgt.offsetLeft;
1022 var top = tgt.offsetTop;
1023 var x = (e.clientX - left);
1024 var y = (e.clientY - top);
1025 if (x < screenWidth - 80 || x > screenWidth - 50) {
1026 return false;
1027 }
1028 if (y < 40 || y > screenHeight - 80) {
1029 return false;
1030 }
1031 curveT = (y - 40) / (screenHeight - 120);
1032 if (curveT < 0 || curveT > 1) {
1033 throw "stop execution";
1034 }
1035 return true;
1036 }
1037
caryclark1049f122015-04-20 08:31:59 -07001038 function ptInWControl() {
1039 var e = window.event;
1040 var tgt = e.target || e.srcElement;
1041 var left = tgt.offsetLeft;
1042 var top = tgt.offsetTop;
1043 var x = (e.clientX - left);
1044 var y = (e.clientY - top);
1045 if (x < screenWidth - 40 || x > screenWidth - 10) {
1046 return false;
1047 }
1048 if (y < 40 || y > screenHeight - 80) {
1049 return false;
1050 }
1051 var w = (y - 40) / (screenHeight - 120);
1052 if (w < 0 || w > 1) {
1053 throw "stop execution";
1054 }
1055 var choice = 0;
1056 for (var curves in tests[testIndex]) {
1057 var curve = tests[testIndex][curves];
1058 if (curve.length != 7) {
1059 continue;
1060 }
1061 if (choice == curveW) {
1062 curve[6] = w;
1063 break;
1064 }
1065 ++choice;
1066 }
1067 return true;
1068 }
1069
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001070 function drawTop() {
1071 init(tests[testIndex]);
1072 redraw();
1073 }
1074
1075 function redraw() {
caryclark54359292015-03-26 07:52:43 -07001076 if (focus_on_selection > 0) {
1077 var focusXmin = focusYmin = Infinity;
1078 var focusXmax = focusYmax = -Infinity;
1079 var choice = 0;
1080 for (var curves in tests[testIndex]) {
1081 if (++choice != focus_on_selection) {
1082 continue;
1083 }
1084 var curve = tests[testIndex][curves];
caryclark1049f122015-04-20 08:31:59 -07001085 var last = curve.length - (curve.length % 2 == 1 ? 1 : 0);
caryclark54359292015-03-26 07:52:43 -07001086 for (var idx = 0; idx < last; idx += 2) {
1087 focusXmin = Math.min(focusXmin, curve[idx]);
1088 focusXmax = Math.max(focusXmax, curve[idx]);
1089 focusYmin = Math.min(focusYmin, curve[idx + 1]);
1090 focusYmax = Math.max(focusYmax, curve[idx + 1]);
1091 }
1092 }
1093 focusXmin -= Math.min(1, Math.max(focusXmax - focusXmin, focusYmax - focusYmin));
1094 if (focusXmin < focusXmax && focusYmin < focusYmax) {
1095 setScale(focusXmin, focusXmax, focusYmin, focusYmax);
1096 }
1097 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001098 ctx.beginPath();
1099 ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);
1100 ctx.fillStyle = "white";
1101 ctx.fill();
1102 draw(tests[testIndex], testTitles[testIndex]);
1103 }
1104
1105 function doKeyPress(evt) {
1106 var char = String.fromCharCode(evt.charCode);
caryclark54359292015-03-26 07:52:43 -07001107 var focusWasOn = false;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001108 switch (char) {
1109 case '0':
1110 case '1':
1111 case '2':
1112 case '3':
1113 case '4':
1114 case '5':
1115 case '6':
1116 case '7':
1117 case '8':
1118 case '9':
1119 decimal_places = char - '0';
1120 redraw();
1121 break;
1122 case '-':
caryclark54359292015-03-26 07:52:43 -07001123 focusWasOn = focus_on_selection;
1124 if (focusWasOn) {
1125 focus_on_selection = false;
1126 scale /= 1.2;
1127 } else {
1128 scale /= 2;
1129 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001130 calcLeftTop();
1131 redraw();
caryclark54359292015-03-26 07:52:43 -07001132 focus_on_selection = focusWasOn;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001133 break;
1134 case '=':
1135 case '+':
caryclark54359292015-03-26 07:52:43 -07001136 focusWasOn = focus_on_selection;
1137 if (focusWasOn) {
1138 focus_on_selection = false;
1139 scale *= 1.2;
1140 } else {
1141 scale *= 2;
1142 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001143 calcLeftTop();
1144 redraw();
caryclark54359292015-03-26 07:52:43 -07001145 focus_on_selection = focusWasOn;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001146 break;
caryclarkfa6d6562014-07-29 12:13:28 -07001147 case 'b':
1148 draw_cubic_red ^= true;
1149 redraw();
1150 break;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001151 case 'c':
1152 drawTop();
1153 break;
1154 case 'd':
1155 var test = tests[testIndex];
1156 var testClone = [];
1157 for (var curves in test) {
1158 var c = test[curves];
1159 var cClone = [];
1160 for (var index = 0; index < c.length; ++index) {
1161 cClone.push(c[index]);
1162 }
1163 testClone.push(cClone);
1164 }
1165 tests.push(testClone);
1166 testTitles.push(testTitles[testIndex] + " copy");
1167 testIndex = tests.length - 1;
1168 redraw();
1169 break;
1170 case 'e':
caryclark54359292015-03-26 07:52:43 -07001171 draw_endpoints = (draw_endpoints + 1) % 3;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001172 redraw();
1173 break;
1174 case 'f':
1175 draw_derivative ^= true;
1176 redraw();
1177 break;
1178 case 'i':
1179 draw_ray_intersect = (draw_ray_intersect + 1) % 3;
1180 redraw();
1181 break;
1182 case 'l':
1183 var test = tests[testIndex];
1184 console.log("<div id=\"" + testTitles[testIndex] + "\" >");
1185 for (var curves in test) {
1186 var c = test[curves];
1187 var s = "{{";
1188 for (var i = 0; i < c.length; i += 2) {
1189 s += "{";
1190 s += c[i] + "," + c[i + 1];
1191 s += "}";
1192 if (i + 2 < c.length) {
1193 s += ", ";
1194 }
1195 }
1196 console.log(s + "}},");
1197 }
1198 console.log("</div>");
1199 break;
1200 case 'm':
1201 draw_midpoint = (draw_midpoint + 1) % 3;
1202 redraw();
1203 break;
1204 case 'N':
1205 testIndex += 9;
1206 case 'n':
1207 testIndex = (testIndex + 1) % tests.length;
1208 drawTop();
1209 break;
1210 case 'o':
1211 draw_order ^= true;
1212 redraw();
1213 break;
1214 case 'P':
1215 testIndex -= 9;
1216 case 'p':
1217 if (--testIndex < 0)
1218 testIndex = tests.length - 1;
1219 drawTop();
1220 break;
1221 case 'q':
1222 draw_quarterpoint = (draw_quarterpoint + 1) % 3;
1223 redraw();
1224 break;
1225 case 'r':
1226 for (var i = 0; i < testDivs.length; ++i) {
1227 var title = testDivs[i].id.toString();
1228 if (title == testTitles[testIndex]) {
1229 var str = testDivs[i].firstChild.data;
1230 parse(str, title);
1231 var original = tests.pop();
1232 testTitles.pop();
1233 tests[testIndex] = original;
1234 break;
1235 }
1236 }
1237 redraw();
1238 break;
1239 case 's':
1240 draw_sortpoint = (draw_sortpoint + 1) % 3;
1241 redraw();
1242 break;
1243 case 't':
1244 draw_t ^= true;
1245 redraw();
1246 break;
1247 case 'u':
1248 draw_closest_t ^= true;
1249 redraw();
1250 break;
1251 case 'v':
1252 draw_tangents = (draw_tangents + 1) % 4;
1253 redraw();
1254 break;
caryclark1049f122015-04-20 08:31:59 -07001255 case 'w':
1256 ++curveW;
1257 var choice = 0;
1258 draw_w = false;
1259 for (var curves in tests[testIndex]) {
1260 var curve = tests[testIndex][curves];
1261 if (curve.length != 7) {
1262 continue;
1263 }
1264 if (choice == curveW) {
1265 draw_w = true;
1266 break;
1267 }
1268 ++choice;
1269 }
1270 if (!draw_w) {
1271 curveW = -1;
1272 }
1273 redraw();
1274 break;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001275 case 'x':
1276 draw_point_xy ^= true;
1277 redraw();
1278 break;
1279 case 'y':
1280 draw_mouse_xy ^= true;
1281 redraw();
1282 break;
1283 case '\\':
1284 retina_scale ^= true;
1285 drawTop();
1286 break;
caryclark54359292015-03-26 07:52:43 -07001287 case '`':
1288 ++focus_on_selection;
1289 if (focus_on_selection >= tests[testIndex].length) {
1290 focus_on_selection = 0;
1291 }
1292 setScale(xmin, xmax, ymin, ymax);
1293 redraw();
1294 break;
1295 case '.':
caryclark1049f122015-04-20 08:31:59 -07001296 draw_id = (draw_id + 1) % 3;
caryclark54359292015-03-26 07:52:43 -07001297 redraw();
1298 break;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001299 }
1300 }
1301
1302 function doKeyDown(evt) {
1303 var char = evt.keyCode;
1304 var preventDefault = false;
1305 switch (char) {
1306 case 37: // left arrow
1307 if (evt.shiftKey) {
1308 testIndex -= 9;
1309 }
1310 if (--testIndex < 0)
1311 testIndex = tests.length - 1;
caryclark54359292015-03-26 07:52:43 -07001312 if (evt.ctrlKey) {
1313 redraw();
1314 } else {
1315 drawTop();
1316 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001317 preventDefault = true;
1318 break;
1319 case 39: // right arrow
1320 if (evt.shiftKey) {
1321 testIndex += 9;
1322 }
1323 if (++testIndex >= tests.length)
1324 testIndex = 0;
caryclark54359292015-03-26 07:52:43 -07001325 if (evt.ctrlKey) {
1326 redraw();
1327 } else {
1328 drawTop();
1329 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001330 preventDefault = true;
1331 break;
1332 }
1333 if (preventDefault) {
1334 evt.preventDefault();
1335 return false;
1336 }
1337 return true;
1338 }
1339
1340 function calcXY() {
1341 var e = window.event;
1342 var tgt = e.target || e.srcElement;
1343 var left = tgt.offsetLeft;
1344 var top = tgt.offsetTop;
1345 mouseX = (e.clientX - left) / scale + srcLeft;
1346 mouseY = (e.clientY - top) / scale + srcTop;
1347 }
1348
1349 function calcLeftTop() {
1350 srcLeft = mouseX - screenWidth / 2 / scale;
1351 srcTop = mouseY - screenHeight / 2 / scale;
1352 }
1353
1354 function handleMouseClick() {
caryclark1049f122015-04-20 08:31:59 -07001355 if ((!draw_t || !ptInTControl()) && (!draw_w || !ptInWControl())) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001356 calcXY();
1357 } else {
1358 redraw();
1359 }
1360 }
1361
1362 function initDown() {
1363 var test = tests[testIndex];
1364 var bestDistance = 1000000;
1365 activePt = -1;
1366 for (var curves in test) {
1367 var testCurve = test[curves];
caryclark1049f122015-04-20 08:31:59 -07001368 if (testCurve.length != 4 && (testCurve.length < 6 || testCurve.length > 8)) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001369 continue;
1370 }
caryclark1049f122015-04-20 08:31:59 -07001371 var testMax = testCurve.length == 7 ? 6 : testCurve.length;
1372 for (var i = 0; i < testMax; i += 2) {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +00001373 var testX = testCurve[i];
1374 var testY = testCurve[i + 1];
1375 var dx = testX - mouseX;
1376 var dy = testY - mouseY;
1377 var dist = dx * dx + dy * dy;
1378 if (dist > bestDistance) {
1379 continue;
1380 }
1381 activeCurve = testCurve;
1382 activePt = i;
1383 bestDistance = dist;
1384 }
1385 }
1386 if (activePt >= 0) {
1387 lastX = mouseX;
1388 lastY = mouseY;
1389 }
1390 }
1391
1392 function handleMouseOver() {
1393 calcXY();
1394 if (draw_mouse_xy) {
1395 var num = mouseX.toFixed(decimal_places) + ", " + mouseY.toFixed(decimal_places);
1396 ctx.beginPath();
1397 ctx.rect(300, 100, num.length * 6, 10);
1398 ctx.fillStyle = "white";
1399 ctx.fill();
1400 ctx.font = "normal 10px Arial";
1401 ctx.fillStyle = "black";
1402 ctx.textAlign = "left";
1403 ctx.fillText(num, 300, 108);
1404 }
1405 if (!mouseDown) {
1406 activePt = -1;
1407 return;
1408 }
1409 if (activePt < 0) {
1410 initDown();
1411 return;
1412 }
1413 var deltaX = mouseX - lastX;
1414 var deltaY = mouseY - lastY;
1415 lastX = mouseX;
1416 lastY = mouseY;
1417 if (activePt == 0) {
1418 var test = tests[testIndex];
1419 for (var curves in test) {
1420 var testCurve = test[curves];
1421 testCurve[0] += deltaX;
1422 testCurve[1] += deltaY;
1423 }
1424 } else {
1425 activeCurve[activePt] += deltaX;
1426 activeCurve[activePt + 1] += deltaY;
1427 }
1428 redraw();
1429 }
1430
1431 function start() {
1432 for (var i = 0; i < testDivs.length; ++i) {
1433 var title = testDivs[i].id.toString();
1434 var str = testDivs[i].firstChild.data;
1435 parse(str, title);
1436 }
1437 drawTop();
1438 window.addEventListener('keypress', doKeyPress, true);
1439 window.addEventListener('keydown', doKeyDown, true);
1440 window.onresize = function () {
1441 drawTop();
1442 }
1443 }
1444
1445</script>
1446</head>
1447
1448<body onLoad="start();">
1449
1450<canvas id="canvas" width="750" height="500"
1451 onmousedown="mouseDown = true"
1452 onmouseup="mouseDown = false"
1453 onmousemove="handleMouseOver()"
1454 onclick="handleMouseClick()"
1455 ></canvas >
1456</body>
1457</html>