ccec0f958f
R=reed@google.com marked 'no commit' to attempt to get trybots to run TBR=reed@google.com Review URL: https://codereview.chromium.org/1002693002
1439 lines
54 KiB
HTML
1439 lines
54 KiB
HTML
<!DOCTYPE html>
|
|
|
|
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<title></title>
|
|
<div style="height:0">
|
|
<div id="sect1">
|
|
{{{1.80943513, 3.0778243500000002}, {1.66686702, 2.1680693600000001}, {1.68301272, 0}, {3, 0}}} id=1
|
|
{{{0, 1}, {0, 2}, {0.75, 2.25}, {1.75, 2.25}}} id=2
|
|
{{{1.75, 2.25}, {2.75, 2.25}, {4, 2}, {5, 2}}} id=4
|
|
</div>
|
|
|
|
<div id="sect2">
|
|
{{{1.80943513, 3.0778243500000002}, {1.66686702, 2.1680693600000001}, {1.68301272, 0}, {3, 0}}} id=1
|
|
{{{0, 1}, {0, 2}, {0.75, 2.25}, {1.75, 2.25}}} id=2
|
|
{{{1.75, 2.25}, {2.2500000000000018, 2.25}, {2.8124999999999991, 2.1875}, {3.375, 2.125}}} id=4
|
|
</div>
|
|
|
|
<div id="sect3">
|
|
{{{1.80943513, 3.0778243500000002}, {1.738151075, 2.6229468550000004}, {1.7065454725, 1.8534907675000001}, {1.85738429375, 1.19775405375}}} id=1
|
|
{{{0, 1}, {0, 2}, {0.75, 2.25}, {1.75, 2.25}}} id=2
|
|
{{{1.75, 2.25}, {2.2500000000000018, 2.25}, {2.8124999999999991, 2.1875}, {3.375, 2.125}}} id=4
|
|
</div>
|
|
|
|
<div id="sect4">
|
|
{{{1.80943513, 3.0778243500000002}, {1.7737931025, 2.8503856025000003}, {1.7480706881249999, 2.5443022068750003}, {1.7501136332812499, 2.2131114089062502}}} id=1
|
|
{{{0, 1}, {0, 2}, {0.75, 2.25}, {1.75, 2.25}}} id=2
|
|
{{{1.75, 2.25}, {2.2500000000000018, 2.25}, {2.8124999999999991, 2.1875}, {3.375, 2.125}}} id=4
|
|
</div>
|
|
|
|
<div id="sect5">
|
|
{{{1.80943513, 3.0778243500000002}, {1.7737931025, 2.8503856025000003}, {1.7480706881249999, 2.5443022068750003}, {1.7501136332812499, 2.2131114089062502}}} id=1
|
|
{{{0.5, 2}, {0.81249999999999956, 2.1874999999999987}, {1.2500000000000009, 2.2500000000000013}, {1.75, 2.25}}} id=8
|
|
{{{1.75, 2.25}, {2.2500000000000018, 2.25}, {2.8124999999999991, 2.1875}, {3.375, 2.125}}} id=4
|
|
</div>
|
|
|
|
<div id="sect6">
|
|
{{{1.80943513, 3.0778243500000002}, {1.7737931025, 2.8503856025000003}, {1.7480706881249999, 2.5443022068750003}, {1.7501136332812499, 2.2131114089062502}}} id=1
|
|
{{{0.5, 2}, {0.81249999999999956, 2.1874999999999987}, {1.2500000000000009, 2.2500000000000013}, {1.75, 2.25}}} id=8
|
|
{{{1.75, 2.25}, {1.9999999999999976, 2.25}, {2.2656250000000022, 2.234375}, {2.5390625, 2.2109375}}} id=4
|
|
</div>
|
|
|
|
<div id="sect7">
|
|
{{{1.80943513, 3.0778243500000002}, {1.7737931025, 2.8503856025000003}, {1.7480706881249999, 2.5443022068750003}, {1.7501136332812499, 2.2131114089062502}}} id=1
|
|
{{{1.0546875, 2.1953125}, {1.2656250000000009, 2.2343750000000009}, {1.4999999999999996, 2.2499999999999996}, {1.75, 2.25}}} id=12
|
|
{{{1.75, 2.25}, {1.9999999999999976, 2.25}, {2.2656250000000022, 2.234375}, {2.5390625, 2.2109375}}} id=4
|
|
</div>
|
|
|
|
<div id="sect8">
|
|
{{{1.7656425168945311, 2.6843748983789064}, {1.7550120280078125, 2.5380253562890633}, {1.7490921607031253, 2.3787068078906248}, {1.7501136332812499, 2.2131114089062502}}} id=7
|
|
{{{1.0546875, 2.1953125}, {1.2656250000000009, 2.2343750000000009}, {1.4999999999999996, 2.2499999999999996}, {1.75, 2.25}}} id=12
|
|
{{{1.75, 2.25}, {1.9999999999999976, 2.25}, {2.2656250000000022, 2.234375}, {2.5390625, 2.2109375}}} id=4
|
|
</div>
|
|
|
|
<div id="sect9">
|
|
{{{1.7656425168945311, 2.6843748983789064}, {1.7550120280078125, 2.5380253562890633}, {1.7490921607031253, 2.3787068078906248}, {1.7501136332812499, 2.2131114089062502}}} id=7
|
|
{{{1.0546875, 2.1953125}, {1.2656250000000009, 2.2343750000000009}, {1.4999999999999996, 2.2499999999999996}, {1.75, 2.25}}} id=12
|
|
{{{1.75, 2.25}, {1.8750000000000016, 2.2499999999999991}, {2.0039062499999982, 2.2460937500000004}, {2.1357421875, 2.2392578125}}} id=4
|
|
</div>
|
|
|
|
<div id="sect10">
|
|
{{{1.7656425168945311, 2.6843748983789064}, {1.7550120280078125, 2.5380253562890633}, {1.7490921607031253, 2.3787068078906248}, {1.7501136332812499, 2.2131114089062502}}} id=7
|
|
{{{1.3876953125, 2.2373046875}, {1.50390625, 2.24609375}, {1.625, 2.25}, {1.75, 2.25}}} id=16
|
|
{{{1.75, 2.25}, {1.8750000000000016, 2.2499999999999991}, {2.0039062499999982, 2.2460937500000004}, {2.1357421875, 2.2392578125}}} id=4
|
|
</div>
|
|
|
|
<div id="sect11">
|
|
{{{1.7535085895385742, 2.4559603499780276}, {1.7508274956738286, 2.3771375952441409}, {1.7496028969921869, 2.2959091083984369}, {1.7501136332812499, 2.2131114089062502}}} id=9
|
|
{{{1.3876953125, 2.2373046875}, {1.50390625, 2.24609375}, {1.625, 2.25}, {1.75, 2.25}}} id=16
|
|
{{{1.75, 2.25}, {1.8750000000000016, 2.2499999999999991}, {2.0039062499999982, 2.2460937500000004}, {2.1357421875, 2.2392578125}}} id=4
|
|
</div>
|
|
|
|
<div id="sect12">
|
|
{{{1.7535085895385742, 2.4559603499780276}, {1.7508274956738286, 2.3771375952441409}, {1.7496028969921869, 2.2959091083984369}, {1.7501136332812499, 2.2131114089062502}}} id=9
|
|
{{{1.3876953125, 2.2373046875}, {1.50390625, 2.24609375}, {1.625, 2.25}, {1.75, 2.25}}} id=16
|
|
{{{1.75, 2.25}, {1.8124999999999989, 2.2499999999999996}, {1.8759765625000011, 2.2490234374999996}, {1.9403076171875, 2.2471923828125}}} id=4
|
|
</div>
|
|
|
|
<div id="sect13">
|
|
{{{1.7535085895385742, 2.4559603499780276}, {1.7508274956738286, 2.3771375952441409}, {1.7496028969921869, 2.2959091083984369}, {1.7501136332812499, 2.2131114089062502}}} id=9
|
|
{{{1.5655517578125, 2.2469482421875}, {1.6259765625, 2.2490234375}, {1.6875, 2.25}, {1.75, 2.25}}} id=20
|
|
{{{1.75, 2.25}, {1.8124999999999989, 2.2499999999999996}, {1.8759765625000011, 2.2490234374999996}, {1.9403076171875, 2.2471923828125}}} id=4
|
|
</div>
|
|
|
|
<div id="sect14">
|
|
{{{1.7506141751022339, 2.3360264837265015}, {1.7500367307348632, 2.2955168052368169}, {1.7498582651367187, 2.2545102586523438}, {1.7501136332812499, 2.2131114089062502}}} id=11
|
|
{{{1.5655517578125, 2.2469482421875}, {1.6259765625, 2.2490234375}, {1.6875, 2.25}, {1.75, 2.25}}} id=20
|
|
{{{1.75, 2.25}, {1.8124999999999989, 2.2499999999999996}, {1.8759765625000011, 2.2490234374999996}, {1.9403076171875, 2.2471923828125}}} id=4
|
|
</div>
|
|
|
|
<div id="sect15">
|
|
{{{1.7506141751022339, 2.3360264837265015}, {1.7500367307348632, 2.2955168052368169}, {1.7498582651367187, 2.2545102586523438}, {1.7501136332812499, 2.2131114089062502}}} id=11
|
|
{{{1.5655517578125, 2.2469482421875}, {1.6259765625, 2.2490234375}, {1.6875, 2.25}, {1.75, 2.25}}} id=20
|
|
{{{1.75, 2.25}, {1.7812500000000011, 2.2500000000000004}, {1.8127441406249989, 2.2497558593749991}, {1.8444671630859375, 2.2492828369140625}}} id=4
|
|
</div>
|
|
|
|
<div id="sect16">
|
|
{{{1.7506141751022339, 2.3360264837265015}, {1.7500367307348632, 2.2955168052368169}, {1.7498582651367187, 2.2545102586523438}, {1.7501136332812499, 2.2131114089062502}}} id=11
|
|
{{{1.6569976806640625, 2.2492523193359375}, {1.687744140625, 2.249755859375}, {1.71875, 2.25}, {1.75, 2.25}}} id=24
|
|
{{{1.75, 2.25}, {1.7812500000000011, 2.2500000000000004}, {1.8127441406249989, 2.2497558593749991}, {1.8444671630859375, 2.2492828369140625}}} id=4
|
|
</div>
|
|
|
|
<div id="sect17">
|
|
{{{1.7500515994997787, 2.274902385537529}, {1.7499667235723877, 2.2544121828619379}, {1.7499859492089846, 2.2338108337792986}, {1.7501136332812499, 2.2131114089062502}}} id=13
|
|
{{{1.6569976806640625, 2.2492523193359375}, {1.687744140625, 2.249755859375}, {1.71875, 2.25}, {1.75, 2.25}}} id=24
|
|
{{{1.75, 2.25}, {1.7812500000000011, 2.2500000000000004}, {1.8127441406249989, 2.2497558593749991}, {1.8444671630859375, 2.2492828369140625}}} id=4
|
|
</div>
|
|
|
|
<div id="sect18">
|
|
{{{1.7500515994997787, 2.274902385537529}, {1.7499667235723877, 2.2544121828619379}, {1.7499859492089846, 2.2338108337792986}, {1.7501136332812499, 2.2131114089062502}}} id=13
|
|
{{{1.6569976806640625, 2.2492523193359375}, {1.687744140625, 2.249755859375}, {1.71875, 2.25}, {1.75, 2.25}}} id=24
|
|
{{{1.75, 2.25}, {1.7656249999999989, 2.25}, {1.7813110351562511, 2.24993896484375}, {1.7970561981201172, 2.2498188018798828}}} id=4
|
|
</div>
|
|
|
|
<div id="sect19">
|
|
{{{1.7500515994997787, 2.274902385537529}, {1.7499667235723877, 2.2544121828619379}, {1.7499859492089846, 2.2338108337792986}, {1.7501136332812499, 2.2131114089062502}}} id=13
|
|
{{{1.7033100128173828, 2.2498149871826172}, {1.7188110351562504, 2.2499389648437504}, {1.7343749999999991, 2.2499999999999991}, {1.75, 2.25}}} id=28
|
|
{{{1.75, 2.25}, {1.7656249999999989, 2.25}, {1.7813110351562511, 2.24993896484375}, {1.7970561981201172, 2.2498188018798828}}} id=4
|
|
</div>
|
|
|
|
<div id="sect20">
|
|
{{{1.7500515994997787, 2.274902385537529}, {1.7500091615360831, 2.2646572841997337}, {1.7499927489633849, 2.2543843962601757}, {1.7500029063906433, 2.2440853555459359}}} id=13
|
|
{{{1.7033100128173828, 2.2498149871826172}, {1.7188110351562504, 2.2499389648437504}, {1.7343749999999991, 2.2499999999999991}, {1.75, 2.25}}} id=28
|
|
{{{1.75, 2.25}, {1.7656249999999989, 2.25}, {1.7813110351562511, 2.24993896484375}, {1.7970561981201172, 2.2498188018798828}}} id=4
|
|
</div>
|
|
|
|
<div id="sect21">
|
|
{{{1.7500515994997787, 2.274902385537529}, {1.7500091615360831, 2.2646572841997337}, {1.7499927489633849, 2.2543843962601757}, {1.7500029063906433, 2.2440853555459359}}} id=13
|
|
{{{1.7033100128173828, 2.2498149871826172}, {1.7188110351562504, 2.2499389648437504}, {1.7343749999999991, 2.2499999999999991}, {1.75, 2.25}}} id=28
|
|
{{{1.75, 2.25}, {1.7578125000000004, 2.25}, {1.7656402587890616, 2.2499847412109375}, {1.7734830379486084, 2.2499544620513916}}} id=4
|
|
</div>
|
|
|
|
<div id="sect22">
|
|
{{{1.7500515994997787, 2.274902385537529}, {1.7500091615360831, 2.2646572841997337}, {1.7499927489633849, 2.2543843962601757}, {1.7500029063906433, 2.2440853555459359}}} id=13
|
|
{{{1.7266085147857666, 2.2499539852142334}, {1.7343902587890614, 2.2499847412109362}, {1.7421875000000011, 2.2500000000000013}, {1.75, 2.25}}} id=32
|
|
{{{1.75, 2.25}, {1.7578125000000004, 2.25}, {1.7656402587890616, 2.2499847412109375}, {1.7734830379486084, 2.2499544620513916}}} id=4
|
|
</div>
|
|
|
|
<div id="sect23">
|
|
{{{1.7500075296736033, 2.2595140978078994}, {1.749999391463374, 2.2543778580665053}, {1.7499978276770138, 2.2492348759030554}, {1.7500029063906433, 2.2440853555459359}}} id=17
|
|
{{{1.7266085147857666, 2.2499539852142334}, {1.7343902587890614, 2.2499847412109362}, {1.7421875000000011, 2.2500000000000013}, {1.75, 2.25}}} id=32
|
|
{{{1.75, 2.25}, {1.7578125000000004, 2.25}, {1.7656402587890616, 2.2499847412109375}, {1.7734830379486084, 2.2499544620513916}}} id=4
|
|
</div>
|
|
|
|
<div id="sect24">
|
|
{{{1.7500075296736033, 2.2595140978078994}, {1.749999391463374, 2.2543778580665053}, {1.7499978276770138, 2.2492348759030554}, {1.7500029063906433, 2.2440853555459359}}} id=17
|
|
{{{1.7266085147857666, 2.2499539852142334}, {1.7343902587890614, 2.2499847412109362}, {1.7421875000000011, 2.2500000000000013}, {1.75, 2.25}}} id=32
|
|
{{{1.75, 2.25}, {1.7539062499999991, 2.25}, {1.7578163146972661, 2.2499961853027344}, {1.7617301642894745, 2.2499885857105255}}} id=4
|
|
</div>
|
|
|
|
<div id="sect25">
|
|
{{{1.7500075296736033, 2.2595140978078994}, {1.749999391463374, 2.2543778580665053}, {1.7499978276770138, 2.2492348759030554}, {1.7500029063906433, 2.2440853555459359}}} id=17
|
|
{{{1.7382927238941193, 2.2499885261058807}, {1.7421913146972665, 2.2499961853027353}, {1.7460937499999996, 2.2499999999999996}, {1.75, 2.25}}} id=36
|
|
{{{1.75, 2.25}, {1.7539062499999991, 2.25}, {1.7578163146972661, 2.2499961853027344}, {1.7617301642894745, 2.2499885857105255}}} id=4
|
|
</div>
|
|
|
|
<div id="sect26">
|
|
{{{1.7500002616856762, 2.2518047069078144}, {1.7499994883020116, 2.2492332413546396}, {1.7500003670338282, 2.2466601157244943}, {1.7500029063906433, 2.2440853555459359}}} id=19
|
|
{{{1.75, 2.25}, {1.7539062499999991, 2.25}, {1.7578163146972661, 2.2499961853027344}, {1.7617301642894745, 2.2499885857105255}}} id=4
|
|
</div>
|
|
|
|
<div id="sect27">
|
|
{{{1.7500002616856762, 2.2518047069078144}, {1.7499994883020116, 2.2492332413546396}, {1.7500003670338282, 2.2466601157244943}, {1.7500029063906433, 2.2440853555459359}}} id=19
|
|
{{{1.75, 2.25}, {1.7519531250000011, 2.2499999999999991}, {1.7539072036743153, 2.249999046325684}, {1.7558622322976589, 2.2499971427023411}}} id=4
|
|
</div>
|
|
|
|
<div id="sect28">
|
|
{{{1.7500002616856762, 2.2518047069078144}, {1.749999874993843, 2.2505189741312273}, {1.7499999013308822, 2.2492328263353962}, {1.7500003417604797, 2.2479462667113941}}} id=19
|
|
{{{1.75, 2.25}, {1.7519531250000011, 2.2499999999999991}, {1.7539072036743153, 2.249999046325684}, {1.7558622322976589, 2.2499971427023411}}} id=4
|
|
</div>
|
|
|
|
<div id="sect29">
|
|
{{{1.7500002616856762, 2.2518047069078144}, {1.749999874993843, 2.2505189741312273}, {1.7499999013308822, 2.2492328263353962}, {1.7500003417604797, 2.2479462667113941}}} id=19
|
|
{{{1.75, 2.25}, {1.7509765624999989, 2.2499999999999996}, {1.7519533634185802, 2.2499997615814205}, {1.752930402290076, 2.249999285209924}}} id=4
|
|
</div>
|
|
|
|
<div id="sect30">
|
|
{{{1.7500002616856762, 2.2518047069078144}, {1.7500000683397601, 2.2511618405195208}, {1.7499999782510609, 2.2505188703764163}, {1.7499999915525417, 2.2498757968773848}}} id=19
|
|
{{{1.75, 2.25}, {1.7509765624999989, 2.2499999999999996}, {1.7519533634185802, 2.2499997615814205}, {1.752930402290076, 2.249999285209924}}} id=4
|
|
</div>
|
|
|
|
<div id="sect31">
|
|
{{{1.7500002616856762, 2.2518047069078144}, {1.7500000683397601, 2.2511618405195208}, {1.7499999782510609, 2.2505188703764163}, {1.7499999915525417, 2.2498757968773848}}} id=19
|
|
{{{1.75, 2.25}, {1.7504882812500011, 2.2500000000000004}, {1.7509766221046437, 2.2499999403953543}, {1.7514650225057267, 2.2499998212442733}}} id=4
|
|
</div>
|
|
|
|
<div id="sect32">
|
|
{{{1.7500000491263352, 2.2508403295591259}, {1.7500000040986061, 2.2505188445374351}, {1.7499999849018013, 2.2501973336269003}, {1.7499999915525417, 2.2498757968773848}}} id=25
|
|
{{{1.75, 2.25}, {1.7504882812500011, 2.2500000000000004}, {1.7509766221046437, 2.2499999403953543}, {1.7514650225057267, 2.2499998212442733}}} id=4
|
|
</div>
|
|
|
|
<div id="sect33">
|
|
{{{1.7500000491263352, 2.2508403295591259}, {1.7500000040986061, 2.2505188445374351}, {1.7499999849018013, 2.2501973336269003}, {1.7499999915525417, 2.2498757968773848}}} id=25
|
|
{{{1.75, 2.25}, {1.7502441406249989, 2.25}, {1.7504882961511623, 2.2499999850988388}, {1.7507324665712076, 2.2499999553037924}}} id=4
|
|
</div>
|
|
|
|
<div id="sect34">
|
|
{{{1.7500000009600125, 2.2503580826161893}, {1.7499999913636877, 2.2501973271671556}, {1.7499999882271713, 2.2500365652521426}, {1.7499999915525417, 2.2498757968773848}}} id=27
|
|
{{{1.75, 2.25}, {1.7502441406249989, 2.25}, {1.7504882961511623, 2.2499999850988388}, {1.7507324665712076, 2.2499999553037924}}} id=4
|
|
</div>
|
|
|
|
<div id="sect35">
|
|
{{{1.7500000009600125, 2.2503580826161893}, {1.7499999913636877, 2.2501973271671556}, {1.7499999882271713, 2.2500365652521426}, {1.7499999915525417, 2.2498757968773848}}} id=27
|
|
{{{1.75, 2.25}, {1.7501220703125004, 2.25}, {1.7502441443502894, 2.2499999962747097}, {1.7503662221124614, 2.2499999888250386}}} id=4
|
|
</div>
|
|
|
|
<div id="sect36">
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<script type="text/javascript">
|
|
|
|
var testDivs = [
|
|
sect1,
|
|
sect2,
|
|
sect3,
|
|
sect4,
|
|
sect5,
|
|
sect6,
|
|
sect7,
|
|
sect8,
|
|
sect9,
|
|
sect10,
|
|
sect11,
|
|
sect12,
|
|
sect13,
|
|
sect14,
|
|
sect15,
|
|
sect16,
|
|
sect17,
|
|
sect18,
|
|
sect19,
|
|
sect20,
|
|
sect21,
|
|
sect22,
|
|
sect23,
|
|
sect24,
|
|
sect25,
|
|
sect26,
|
|
sect27,
|
|
sect28,
|
|
sect29,
|
|
sect30,
|
|
sect31,
|
|
sect32,
|
|
sect33,
|
|
sect34,
|
|
sect35,
|
|
sect36,
|
|
|
|
|
|
];
|
|
|
|
var decimal_places = 3;
|
|
|
|
var tests = [];
|
|
var testTitles = [];
|
|
var testIndex = 0;
|
|
var ctx;
|
|
|
|
var subscale = 1;
|
|
var xmin, xmax, ymin, ymax;
|
|
var scale;
|
|
var initScale;
|
|
var mouseX, mouseY;
|
|
var mouseDown = false;
|
|
var srcLeft, srcTop;
|
|
var screenWidth, screenHeight;
|
|
var drawnPts;
|
|
var curveT = 0;
|
|
|
|
var lastX, lastY;
|
|
var activeCurve = [];
|
|
var activePt;
|
|
var ids = [];
|
|
|
|
var focus_on_selection = 0;
|
|
var draw_t = false;
|
|
var draw_closest_t = false;
|
|
var draw_cubic_red = false;
|
|
var draw_derivative = false;
|
|
var draw_endpoints = 2;
|
|
var draw_id = false;
|
|
var draw_midpoint = 0;
|
|
var draw_mouse_xy = false;
|
|
var draw_order = false;
|
|
var draw_point_xy = false;
|
|
var draw_ray_intersect = false;
|
|
var draw_quarterpoint = 0;
|
|
var draw_tangents = 1;
|
|
var draw_sortpoint = 0;
|
|
var retina_scale = !!window.devicePixelRatio;
|
|
|
|
function parse(test, title) {
|
|
var curveStrs = test.split("{{");
|
|
var pattern = /-?\d+\.*\d*e?-?\d*/g;
|
|
var curves = [];
|
|
for (var c in curveStrs) {
|
|
var curveStr = curveStrs[c];
|
|
var idPart = curveStr.split("id=");
|
|
var id = -1;
|
|
if (idPart.length == 2) {
|
|
id = parseInt(idPart[1]);
|
|
curveStr = idPart[0];
|
|
}
|
|
var points = curveStr.match(pattern);
|
|
var pts = [];
|
|
for (var wd in points) {
|
|
var num = parseFloat(points[wd]);
|
|
if (isNaN(num)) continue;
|
|
pts.push(num);
|
|
}
|
|
if (pts.length > 2) {
|
|
curves.push(pts);
|
|
}
|
|
if (id >= 0) {
|
|
ids.push(id);
|
|
ids.push(pts);
|
|
}
|
|
}
|
|
if (curves.length >= 1) {
|
|
tests.push(curves);
|
|
testTitles.push(title);
|
|
}
|
|
}
|
|
|
|
function init(test) {
|
|
var canvas = document.getElementById('canvas');
|
|
if (!canvas.getContext) return;
|
|
ctx = canvas.getContext('2d');
|
|
var resScale = retina_scale && window.devicePixelRatio ? window.devicePixelRatio : 1;
|
|
var unscaledWidth = window.innerWidth - 20;
|
|
var unscaledHeight = window.innerHeight - 20;
|
|
screenWidth = unscaledWidth;
|
|
screenHeight = unscaledHeight;
|
|
canvas.width = unscaledWidth * resScale;
|
|
canvas.height = unscaledHeight * resScale;
|
|
canvas.style.width = unscaledWidth + 'px';
|
|
canvas.style.height = unscaledHeight + 'px';
|
|
if (resScale != 1) {
|
|
ctx.scale(resScale, resScale);
|
|
}
|
|
xmin = Infinity;
|
|
xmax = -Infinity;
|
|
ymin = Infinity;
|
|
ymax = -Infinity;
|
|
for (var curves in test) {
|
|
var curve = test[curves];
|
|
var last = curve.length;
|
|
for (var idx = 0; idx < last; idx += 2) {
|
|
xmin = Math.min(xmin, curve[idx]);
|
|
xmax = Math.max(xmax, curve[idx]);
|
|
ymin = Math.min(ymin, curve[idx + 1]);
|
|
ymax = Math.max(ymax, curve[idx + 1]);
|
|
}
|
|
}
|
|
xmin -= Math.min(1, Math.max(xmax - xmin, ymax - ymin));
|
|
var testW = xmax - xmin;
|
|
var testH = ymax - ymin;
|
|
subscale = 1;
|
|
while (testW * subscale < 0.1 && testH * subscale < 0.1) {
|
|
subscale *= 10;
|
|
}
|
|
while (testW * subscale > 10 && testH * subscale > 10) {
|
|
subscale /= 10;
|
|
}
|
|
setScale(xmin, xmax, ymin, ymax);
|
|
mouseX = (screenWidth / 2) / scale + srcLeft;
|
|
mouseY = (screenHeight / 2) / scale + srcTop;
|
|
initScale = scale;
|
|
}
|
|
|
|
function setScale(x0, x1, y0, y1) {
|
|
var srcWidth = x1 - x0;
|
|
var srcHeight = y1 - y0;
|
|
var usableWidth = screenWidth;
|
|
var xDigits = Math.ceil(Math.log(Math.abs(xmax)) / Math.log(10));
|
|
var yDigits = Math.ceil(Math.log(Math.abs(ymax)) / Math.log(10));
|
|
usableWidth -= (xDigits + yDigits) * 10;
|
|
usableWidth -= decimal_places * 10;
|
|
var hscale = usableWidth / srcWidth;
|
|
var vscale = screenHeight / srcHeight;
|
|
scale = Math.min(hscale, vscale);
|
|
var invScale = 1 / scale;
|
|
var sxmin = x0 - invScale * 5;
|
|
var symin = y0 - invScale * 10;
|
|
var sxmax = x1 + invScale * (6 * decimal_places + 10);
|
|
var symax = y1 + invScale * 10;
|
|
srcWidth = sxmax - sxmin;
|
|
srcHeight = symax - symin;
|
|
hscale = usableWidth / srcWidth;
|
|
vscale = screenHeight / srcHeight;
|
|
scale = Math.min(hscale, vscale);
|
|
srcLeft = sxmin;
|
|
srcTop = symin;
|
|
}
|
|
|
|
function dxy_at_t(curve, t) {
|
|
var dxy = {};
|
|
if (curve.length == 6) {
|
|
var a = t - 1;
|
|
var b = 1 - 2 * t;
|
|
var c = t;
|
|
dxy.x = a * curve[0] + b * curve[2] + c * curve[4];
|
|
dxy.y = a * curve[1] + b * curve[3] + c * curve[5];
|
|
} else if (curve.length == 8) {
|
|
var one_t = 1 - t;
|
|
var a = curve[0];
|
|
var b = curve[2];
|
|
var c = curve[4];
|
|
var d = curve[6];
|
|
dxy.x = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
|
|
a = curve[1];
|
|
b = curve[3];
|
|
c = curve[5];
|
|
d = curve[7];
|
|
dxy.y = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
|
|
}
|
|
return dxy;
|
|
}
|
|
|
|
var flt_epsilon = 1.19209290E-07;
|
|
|
|
function approximately_zero(A) {
|
|
return Math.abs(A) < flt_epsilon;
|
|
}
|
|
|
|
function approximately_zero_inverse(A) {
|
|
return Math.abs(A) > (1 / flt_epsilon);
|
|
}
|
|
|
|
function quad_real_roots(A, B, C) {
|
|
var s = [];
|
|
var p = B / (2 * A);
|
|
var q = C / A;
|
|
if (approximately_zero(A) && (approximately_zero_inverse(p)
|
|
|| approximately_zero_inverse(q))) {
|
|
if (approximately_zero(B)) {
|
|
if (C == 0) {
|
|
s[0] = 0;
|
|
}
|
|
return s;
|
|
}
|
|
s[0] = -C / B;
|
|
return s;
|
|
}
|
|
/* normal form: x^2 + px + q = 0 */
|
|
var p2 = p * p;
|
|
if (!approximately_zero(p2 - q) && p2 < q) {
|
|
return s;
|
|
}
|
|
var sqrt_D = 0;
|
|
if (p2 > q) {
|
|
sqrt_D = Math.sqrt(p2 - q);
|
|
}
|
|
s[0] = sqrt_D - p;
|
|
var flip = -sqrt_D - p;
|
|
if (!approximately_zero(s[0] - flip)) {
|
|
s[1] = flip;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
function cubic_real_roots(A, B, C, D) {
|
|
if (approximately_zero(A)) { // we're just a quadratic
|
|
return quad_real_roots(B, C, D);
|
|
}
|
|
if (approximately_zero(D)) { // 0 is one root
|
|
var s = quad_real_roots(A, B, C);
|
|
for (var i = 0; i < s.length; ++i) {
|
|
if (approximately_zero(s[i])) {
|
|
return s;
|
|
}
|
|
}
|
|
s.push(0);
|
|
return s;
|
|
}
|
|
if (approximately_zero(A + B + C + D)) { // 1 is one root
|
|
var s = quad_real_roots(A, A + B, -D);
|
|
for (var i = 0; i < s.length; ++i) {
|
|
if (approximately_zero(s[i] - 1)) {
|
|
return s;
|
|
}
|
|
}
|
|
s.push(1);
|
|
return s;
|
|
}
|
|
var a, b, c;
|
|
var invA = 1 / A;
|
|
a = B * invA;
|
|
b = C * invA;
|
|
c = D * invA;
|
|
var a2 = a * a;
|
|
var Q = (a2 - b * 3) / 9;
|
|
var R = (2 * a2 * a - 9 * a * b + 27 * c) / 54;
|
|
var R2 = R * R;
|
|
var Q3 = Q * Q * Q;
|
|
var R2MinusQ3 = R2 - Q3;
|
|
var adiv3 = a / 3;
|
|
var r;
|
|
var roots = [];
|
|
if (R2MinusQ3 < 0) { // we have 3 real roots
|
|
var theta = Math.acos(R / Math.sqrt(Q3));
|
|
var neg2RootQ = -2 * Math.sqrt(Q);
|
|
r = neg2RootQ * Math.cos(theta / 3) - adiv3;
|
|
roots.push(r);
|
|
r = neg2RootQ * Math.cos((theta + 2 * Math.PI) / 3) - adiv3;
|
|
if (!approximately_zero(roots[0] - r)) {
|
|
roots.push(r);
|
|
}
|
|
r = neg2RootQ * Math.cos((theta - 2 * Math.PI) / 3) - adiv3;
|
|
if (!approximately_zero(roots[0] - r) && (roots.length == 1
|
|
|| !approximately_zero(roots[1] - r))) {
|
|
roots.push(r);
|
|
}
|
|
} else { // we have 1 real root
|
|
var sqrtR2MinusQ3 = Math.sqrt(R2MinusQ3);
|
|
var A = Math.abs(R) + sqrtR2MinusQ3;
|
|
A = Math.pow(A, 1/3);
|
|
if (R > 0) {
|
|
A = -A;
|
|
}
|
|
if (A != 0) {
|
|
A += Q / A;
|
|
}
|
|
r = A - adiv3;
|
|
roots.push(r);
|
|
if (approximately_zero(R2 - Q3)) {
|
|
r = -A / 2 - adiv3;
|
|
if (!approximately_zero(s[0] - r)) {
|
|
roots.push(r);
|
|
}
|
|
}
|
|
}
|
|
return roots;
|
|
}
|
|
|
|
function approximately_zero_or_more(tValue) {
|
|
return tValue >= -flt_epsilon;
|
|
}
|
|
|
|
function approximately_one_or_less(tValue) {
|
|
return tValue <= 1 + flt_epsilon;
|
|
}
|
|
|
|
function approximately_less_than_zero(tValue) {
|
|
return tValue < flt_epsilon;
|
|
}
|
|
|
|
function approximately_greater_than_one(tValue) {
|
|
return tValue > 1 - flt_epsilon;
|
|
}
|
|
|
|
function add_valid_ts(s) {
|
|
var t = [];
|
|
nextRoot:
|
|
for (var index = 0; index < s.length; ++index) {
|
|
var tValue = s[index];
|
|
if (approximately_zero_or_more(tValue) && approximately_one_or_less(tValue)) {
|
|
if (approximately_less_than_zero(tValue)) {
|
|
tValue = 0;
|
|
} else if (approximately_greater_than_one(tValue)) {
|
|
tValue = 1;
|
|
}
|
|
for (var idx2 = 0; idx2 < t.length; ++idx2) {
|
|
if (approximately_zero(t[idx2] - tValue)) {
|
|
continue nextRoot;
|
|
}
|
|
}
|
|
t.push(tValue);
|
|
}
|
|
}
|
|
return t;
|
|
}
|
|
|
|
function quad_roots(A, B, C) {
|
|
var s = quad_real_roots(A, B, C);
|
|
var foundRoots = add_valid_ts(s);
|
|
return foundRoots;
|
|
}
|
|
|
|
function cubic_roots(A, B, C, D) {
|
|
var s = cubic_real_roots(A, B, C, D);
|
|
var foundRoots = add_valid_ts(s);
|
|
return foundRoots;
|
|
}
|
|
|
|
function ray_curve_intersect(startPt, endPt, curve) {
|
|
var adj = endPt[0] - startPt[0];
|
|
var opp = endPt[1] - startPt[1];
|
|
var r = [];
|
|
for (var n = 0; n < curve.length / 2; ++n) {
|
|
r[n] = (curve[n * 2 + 1] - startPt[1]) * adj - (curve[n * 2] - startPt[0]) * opp;
|
|
}
|
|
if (curve.length == 6) {
|
|
var A = r[2];
|
|
var B = r[1];
|
|
var C = r[0];
|
|
A += C - 2 * B; // A = a - 2*b + c
|
|
B -= C; // B = -(b - c)
|
|
return quad_roots(A, 2 * B, C);
|
|
}
|
|
var A = r[3]; // d
|
|
var B = r[2] * 3; // 3*c
|
|
var C = r[1] * 3; // 3*b
|
|
var D = r[0]; // a
|
|
A -= D - C + B; // A = -a + 3*b - 3*c + d
|
|
B += 3 * D - 2 * C; // B = 3*a - 6*b + 3*c
|
|
C -= 3 * D; // C = -3*a + 3*b
|
|
return cubic_roots(A, B, C, D);
|
|
}
|
|
|
|
function x_at_t(curve, t) {
|
|
var one_t = 1 - t;
|
|
if (curve.length == 4) {
|
|
return one_t * curve[0] + t * curve[2];
|
|
}
|
|
var one_t2 = one_t * one_t;
|
|
var t2 = t * t;
|
|
if (curve.length == 6) {
|
|
return one_t2 * curve[0] + 2 * one_t * t * curve[2] + t2 * curve[4];
|
|
}
|
|
var a = one_t2 * one_t;
|
|
var b = 3 * one_t2 * t;
|
|
var c = 3 * one_t * t2;
|
|
var d = t2 * t;
|
|
return a * curve[0] + b * curve[2] + c * curve[4] + d * curve[6];
|
|
}
|
|
|
|
function y_at_t(curve, t) {
|
|
var one_t = 1 - t;
|
|
if (curve.length == 4) {
|
|
return one_t * curve[1] + t * curve[3];
|
|
}
|
|
var one_t2 = one_t * one_t;
|
|
var t2 = t * t;
|
|
if (curve.length == 6) {
|
|
return one_t2 * curve[1] + 2 * one_t * t * curve[3] + t2 * curve[5];
|
|
}
|
|
var a = one_t2 * one_t;
|
|
var b = 3 * one_t2 * t;
|
|
var c = 3 * one_t * t2;
|
|
var d = t2 * t;
|
|
return a * curve[1] + b * curve[3] + c * curve[5] + d * curve[7];
|
|
}
|
|
|
|
function drawPointAtT(curve) {
|
|
var x = x_at_t(curve, curveT);
|
|
var y = y_at_t(curve, curveT);
|
|
drawPoint(x, y);
|
|
}
|
|
|
|
function drawLine(x1, y1, x2, y2) {
|
|
ctx.beginPath();
|
|
ctx.moveTo((x1 - srcLeft) * scale,
|
|
(y1 - srcTop) * scale);
|
|
ctx.lineTo((x2 - srcLeft) * scale,
|
|
(y2 - srcTop) * scale);
|
|
ctx.stroke();
|
|
}
|
|
|
|
function drawPoint(px, py) {
|
|
for (var pts = 0; pts < drawnPts.length; pts += 2) {
|
|
var x = drawnPts[pts];
|
|
var y = drawnPts[pts + 1];
|
|
if (px == x && py == y) {
|
|
return;
|
|
}
|
|
}
|
|
drawnPts.push(px);
|
|
drawnPts.push(py);
|
|
var _px = (px - srcLeft) * scale;
|
|
var _py = (py - srcTop) * scale;
|
|
ctx.beginPath();
|
|
ctx.arc(_px, _py, 3, 0, Math.PI * 2, true);
|
|
ctx.closePath();
|
|
ctx.stroke();
|
|
if (draw_point_xy) {
|
|
var label = px.toFixed(decimal_places) + ", " + py.toFixed(decimal_places);
|
|
ctx.font = "normal 10px Arial";
|
|
ctx.textAlign = "left";
|
|
ctx.fillStyle = "black";
|
|
ctx.fillText(label, _px + 5, _py);
|
|
}
|
|
}
|
|
|
|
function drawPointSolid(px, py) {
|
|
drawPoint(px, py);
|
|
ctx.fillStyle = "rgba(0,0,0, 0.4)";
|
|
ctx.fill();
|
|
}
|
|
|
|
function crossPt(origin, pt1, pt2) {
|
|
return ((pt1[0] - origin[0]) * (pt2[1] - origin[1])
|
|
- (pt1[1] - origin[1]) * (pt2[0] - origin[0])) > 0 ? 0 : 1;
|
|
}
|
|
|
|
// may not work well for cubics
|
|
function curveClosestT(curve, x, y) {
|
|
var closest = -1;
|
|
var closestDist = Infinity;
|
|
var l = Infinity, t = Infinity, r = -Infinity, b = -Infinity;
|
|
for (var i = 0; i < 16; ++i) {
|
|
var testX = x_at_t(curve, i / 16);
|
|
l = Math.min(testX, l);
|
|
r = Math.max(testX, r);
|
|
var testY = y_at_t(curve, i / 16);
|
|
t = Math.min(testY, t);
|
|
b = Math.max(testY, b);
|
|
var dx = testX - x;
|
|
var dy = testY - y;
|
|
var dist = dx * dx + dy * dy;
|
|
if (closestDist > dist) {
|
|
closestDist = dist;
|
|
closest = i;
|
|
}
|
|
}
|
|
var boundsX = r - l;
|
|
var boundsY = b - t;
|
|
var boundsDist = boundsX * boundsX + boundsY * boundsY;
|
|
if (closestDist > boundsDist) {
|
|
return -1;
|
|
}
|
|
console.log("closestDist = " + closestDist + " boundsDist = " + boundsDist
|
|
+ " t = " + closest / 16);
|
|
return closest / 16;
|
|
}
|
|
|
|
function draw(test, title) {
|
|
ctx.font = "normal 50px Arial";
|
|
ctx.textAlign = "left";
|
|
ctx.fillStyle = "rgba(0,0,0, 0.1)";
|
|
ctx.fillText(title, 50, 50);
|
|
ctx.font = "normal 10px Arial";
|
|
// ctx.lineWidth = "1.001"; "0.999";
|
|
var hullStarts = [];
|
|
var hullEnds = [];
|
|
var midSpokes = [];
|
|
var midDist = [];
|
|
var origin = [];
|
|
var shortSpokes = [];
|
|
var shortDist = [];
|
|
var sweeps = [];
|
|
drawnPts = [];
|
|
for (var curves in test) {
|
|
var curve = test[curves];
|
|
origin.push(curve[0]);
|
|
origin.push(curve[1]);
|
|
var startPt = [];
|
|
startPt.push(curve[2]);
|
|
startPt.push(curve[3]);
|
|
hullStarts.push(startPt);
|
|
var endPt = [];
|
|
if (curve.length == 4) {
|
|
endPt.push(curve[2]);
|
|
endPt.push(curve[3]);
|
|
} else if (curve.length == 6) {
|
|
endPt.push(curve[4]);
|
|
endPt.push(curve[5]);
|
|
} else if (curve.length == 8) {
|
|
endPt.push(curve[6]);
|
|
endPt.push(curve[7]);
|
|
}
|
|
hullEnds.push(endPt);
|
|
var sweep = crossPt(origin, startPt, endPt);
|
|
sweeps.push(sweep);
|
|
var midPt = [];
|
|
midPt.push(x_at_t(curve, 0.5));
|
|
midPt.push(y_at_t(curve, 0.5));
|
|
midSpokes.push(midPt);
|
|
var shortPt = [];
|
|
shortPt.push(x_at_t(curve, 0.25));
|
|
shortPt.push(y_at_t(curve, 0.25));
|
|
shortSpokes.push(shortPt);
|
|
var dx = midPt[0] - origin[0];
|
|
var dy = midPt[1] - origin[1];
|
|
var dist = Math.sqrt(dx * dx + dy * dy);
|
|
midDist.push(dist);
|
|
dx = shortPt[0] - origin[0];
|
|
dy = shortPt[1] - origin[1];
|
|
dist = Math.sqrt(dx * dx + dy * dy);
|
|
shortDist.push(dist);
|
|
}
|
|
var intersect = [];
|
|
var useIntersect = false;
|
|
var maxWidth = Math.max(xmax - xmin, ymax - ymin);
|
|
for (var curves in test) {
|
|
var curve = test[curves];
|
|
if (curve.length == 6 || curve.length == 8) {
|
|
var opp = curves == 0 || curves == 1 ? 0 : 1;
|
|
var sects = ray_curve_intersect(origin, hullEnds[opp], curve);
|
|
intersect.push(sects);
|
|
if (sects.length > 1) {
|
|
var intersection = sects[0];
|
|
if (intersection == 0) {
|
|
intersection = sects[1];
|
|
}
|
|
var ix = x_at_t(curve, intersection) - origin[0];
|
|
var iy = y_at_t(curve, intersection) - origin[1];
|
|
var ex = hullEnds[opp][0] - origin[0];
|
|
var ey = hullEnds[opp][1] - origin[1];
|
|
if (ix * ex >= 0 && iy * ey >= 0) {
|
|
var iDist = Math.sqrt(ix * ix + iy * iy);
|
|
var eDist = Math.sqrt(ex * ex + ey * ey);
|
|
var delta = Math.abs(iDist - eDist) / maxWidth;
|
|
if (delta > (curve.length == 6 ? 1e-5 : 1e-4)) {
|
|
useIntersect ^= true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
var midLeft = curves != 0 ? crossPt(origin, midSpokes[0], midSpokes[1]) : 0;
|
|
var firstInside;
|
|
if (useIntersect) {
|
|
var sect1 = intersect[0].length > 1;
|
|
var sIndex = sect1 ? 0 : 1;
|
|
var sects = intersect[sIndex];
|
|
var intersection = sects[0];
|
|
if (intersection == 0) {
|
|
intersection = sects[1];
|
|
}
|
|
var curve = test[sIndex];
|
|
var ix = x_at_t(curve, intersection) - origin[0];
|
|
var iy = y_at_t(curve, intersection) - origin[1];
|
|
var opp = sect1 ? 1 : 0;
|
|
var ex = hullEnds[opp][0] - origin[0];
|
|
var ey = hullEnds[opp][1] - origin[1];
|
|
var iDist = ix * ix + iy * iy;
|
|
var eDist = ex * ex + ey * ey;
|
|
firstInside = (iDist > eDist) ^ (sIndex == 0) ^ sweeps[0];
|
|
// console.log("iDist=" + iDist + " eDist=" + eDist + " sIndex=" + sIndex
|
|
// + " sweeps[0]=" + sweeps[0]);
|
|
} else {
|
|
// console.log("midLeft=" + midLeft);
|
|
firstInside = midLeft != 0;
|
|
}
|
|
var shorter = midDist[1] < midDist[0];
|
|
var shortLeft = shorter ? crossPt(origin, shortSpokes[0], midSpokes[1])
|
|
: crossPt(origin, midSpokes[0], shortSpokes[1]);
|
|
var startCross = crossPt(origin, hullStarts[0], hullStarts[1]);
|
|
var disallowShort = midLeft == startCross && midLeft == sweeps[0]
|
|
&& midLeft == sweeps[1];
|
|
|
|
// console.log("midLeft=" + midLeft + " startCross=" + startCross);
|
|
var intersectIndex = 0;
|
|
for (var curves in test) {
|
|
var curve = test[curves];
|
|
if (curve.length != 4 && curve.length != 6 && curve.length != 8) {
|
|
continue;
|
|
}
|
|
ctx.lineWidth = 1;
|
|
if (draw_tangents != 0) {
|
|
if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
|
|
ctx.strokeStyle = "rgba(255,0,0, 0.3)";
|
|
} else {
|
|
ctx.strokeStyle = "rgba(0,0,255, 0.3)";
|
|
}
|
|
drawLine(curve[0], curve[1], curve[2], curve[3]);
|
|
if (draw_tangents != 2) {
|
|
if (curve.length > 4) drawLine(curve[2], curve[3], curve[4], curve[5]);
|
|
if (curve.length > 6) drawLine(curve[4], curve[5], curve[6], curve[7]);
|
|
}
|
|
if (draw_tangents != 1) {
|
|
if (curve.length == 6) drawLine(curve[0], curve[1], curve[4], curve[5]);
|
|
if (curve.length == 8) drawLine(curve[0], curve[1], curve[6], curve[7]);
|
|
}
|
|
}
|
|
ctx.beginPath();
|
|
ctx.moveTo((curve[0] - srcLeft) * scale, (curve[1] - srcTop) * scale);
|
|
if (curve.length == 4) {
|
|
ctx.lineTo((curve[2] - srcLeft) * scale, (curve[3] - srcTop) * scale);
|
|
} else if (curve.length == 6) {
|
|
ctx.quadraticCurveTo(
|
|
(curve[2] - srcLeft) * scale, (curve[3] - srcTop) * scale,
|
|
(curve[4] - srcLeft) * scale, (curve[5] - srcTop) * scale);
|
|
} else {
|
|
ctx.bezierCurveTo(
|
|
(curve[2] - srcLeft) * scale, (curve[3] - srcTop) * scale,
|
|
(curve[4] - srcLeft) * scale, (curve[5] - srcTop) * scale,
|
|
(curve[6] - srcLeft) * scale, (curve[7] - srcTop) * scale);
|
|
}
|
|
if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
|
|
ctx.strokeStyle = "rgba(255,0,0, 1)";
|
|
} else {
|
|
ctx.strokeStyle = "rgba(0,0,255, 1)";
|
|
}
|
|
ctx.stroke();
|
|
if (draw_endpoints > 0) {
|
|
drawPoint(curve[0], curve[1]);
|
|
if (draw_endpoints > 1 || curve.length == 4) {
|
|
drawPoint(curve[2], curve[3]);
|
|
}
|
|
if (curve.length == 6 || (draw_endpoints > 1 && curve.length == 8)) {
|
|
drawPoint(curve[4], curve[5]);
|
|
}
|
|
if (curve.length == 8) drawPoint(curve[6], curve[7]);
|
|
}
|
|
if (draw_midpoint != 0) {
|
|
if ((curves == 0) == (midLeft == 0)) {
|
|
ctx.strokeStyle = "rgba(0,180,127, 0.6)";
|
|
} else {
|
|
ctx.strokeStyle = "rgba(127,0,127, 0.6)";
|
|
}
|
|
var midX = x_at_t(curve, 0.5);
|
|
var midY = y_at_t(curve, 0.5);
|
|
drawPointSolid(midX, midY);
|
|
if (draw_midpoint > 1) {
|
|
drawLine(curve[0], curve[1], midX, midY);
|
|
}
|
|
}
|
|
if (draw_quarterpoint != 0) {
|
|
if ((curves == 0) == (shortLeft == 0)) {
|
|
ctx.strokeStyle = "rgba(0,191,63, 0.6)";
|
|
} else {
|
|
ctx.strokeStyle = "rgba(63,0,191, 0.6)";
|
|
}
|
|
var midT = (curves == 0) == shorter ? 0.25 : 0.5;
|
|
var midX = x_at_t(curve, midT);
|
|
var midY = y_at_t(curve, midT);
|
|
drawPointSolid(midX, midY);
|
|
if (draw_quarterpoint > 1) {
|
|
drawLine(curve[0], curve[1], midX, midY);
|
|
}
|
|
}
|
|
if (draw_sortpoint != 0) {
|
|
if ((curves == 0) == ((disallowShort == -1 ? midLeft : shortLeft) == 0)) {
|
|
ctx.strokeStyle = "rgba(0,155,37, 0.6)";
|
|
} else {
|
|
ctx.strokeStyle = "rgba(37,0,155, 0.6)";
|
|
}
|
|
var midT = (curves == 0) == shorter && disallowShort != curves ? 0.25 : 0.5;
|
|
console.log("curves=" + curves + " disallowShort=" + disallowShort
|
|
+ " midLeft=" + midLeft + " shortLeft=" + shortLeft
|
|
+ " shorter=" + shorter + " midT=" + midT);
|
|
var midX = x_at_t(curve, midT);
|
|
var midY = y_at_t(curve, midT);
|
|
drawPointSolid(midX, midY);
|
|
if (draw_sortpoint > 1) {
|
|
drawLine(curve[0], curve[1], midX, midY);
|
|
}
|
|
}
|
|
if (draw_ray_intersect != 0) {
|
|
ctx.strokeStyle = "rgba(75,45,199, 0.6)";
|
|
if (curve.length == 6 || curve.length == 8) {
|
|
var intersections = intersect[intersectIndex];
|
|
for (var i in intersections) {
|
|
var intersection = intersections[i];
|
|
var x = x_at_t(curve, intersection);
|
|
var y = y_at_t(curve, intersection);
|
|
drawPointSolid(x, y);
|
|
if (draw_ray_intersect > 1) {
|
|
drawLine(curve[0], curve[1], x, y);
|
|
}
|
|
}
|
|
}
|
|
++intersectIndex;
|
|
}
|
|
if (draw_order) {
|
|
var px = x_at_t(curve, 0.75);
|
|
var py = y_at_t(curve, 0.75);
|
|
var _px = (px - srcLeft) * scale;
|
|
var _py = (py - srcTop) * scale;
|
|
ctx.beginPath();
|
|
ctx.arc(_px, _py, 15, 0, Math.PI * 2, true);
|
|
ctx.closePath();
|
|
ctx.fillStyle = "white";
|
|
ctx.fill();
|
|
if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
|
|
ctx.strokeStyle = "rgba(255,0,0, 1)";
|
|
ctx.fillStyle = "rgba(255,0,0, 1)";
|
|
} else {
|
|
ctx.strokeStyle = "rgba(0,0,255, 1)";
|
|
ctx.fillStyle = "rgba(0,0,255, 1)";
|
|
}
|
|
ctx.stroke();
|
|
ctx.font = "normal 16px Arial";
|
|
ctx.textAlign = "center";
|
|
ctx.fillText(parseInt(curves) + 1, _px, _py + 5);
|
|
}
|
|
if (draw_closest_t) {
|
|
var t = curveClosestT(curve, mouseX, mouseY);
|
|
if (t >= 0) {
|
|
var x = x_at_t(curve, t);
|
|
var y = y_at_t(curve, t);
|
|
drawPointSolid(x, y);
|
|
}
|
|
}
|
|
if (!approximately_zero(scale - initScale)) {
|
|
ctx.font = "normal 20px Arial";
|
|
ctx.fillStyle = "rgba(0,0,0, 0.3)";
|
|
ctx.textAlign = "right";
|
|
ctx.fillText(scale.toFixed(decimal_places) + 'x',
|
|
screenWidth - 10, screenHeight - 5);
|
|
}
|
|
if (draw_t) {
|
|
drawPointAtT(curve);
|
|
}
|
|
if (draw_id) {
|
|
var id = -1;
|
|
for (var i = 0; i < ids.length; i += 2) {
|
|
if (ids[i + 1] == curve) {
|
|
id = ids[i];
|
|
break;
|
|
}
|
|
}
|
|
if (id >= 0) {
|
|
var px = x_at_t(curve, 0.5);
|
|
var py = y_at_t(curve, 0.5);
|
|
var _px = (px - srcLeft) * scale;
|
|
var _py = (py - srcTop) * scale;
|
|
ctx.beginPath();
|
|
ctx.arc(_px, _py, 15, 0, Math.PI * 2, true);
|
|
ctx.closePath();
|
|
ctx.fillStyle = "white";
|
|
ctx.fill();
|
|
ctx.strokeStyle = "rgba(255,0,0, 1)";
|
|
ctx.fillStyle = "rgba(255,0,0, 1)";
|
|
ctx.stroke();
|
|
ctx.font = "normal 16px Arial";
|
|
ctx.textAlign = "center";
|
|
ctx.fillText(id, _px, _py + 5);
|
|
}
|
|
}
|
|
}
|
|
if (draw_t) {
|
|
drawCurveTControl();
|
|
}
|
|
}
|
|
|
|
function drawCurveTControl() {
|
|
ctx.lineWidth = 2;
|
|
ctx.strokeStyle = "rgba(0,0,0, 0.3)";
|
|
ctx.beginPath();
|
|
ctx.rect(screenWidth - 80, 40, 28, screenHeight - 80);
|
|
ctx.stroke();
|
|
var ty = 40 + curveT * (screenHeight - 80);
|
|
ctx.beginPath();
|
|
ctx.moveTo(screenWidth - 80, ty);
|
|
ctx.lineTo(screenWidth - 85, ty - 5);
|
|
ctx.lineTo(screenWidth - 85, ty + 5);
|
|
ctx.lineTo(screenWidth - 80, ty);
|
|
ctx.fillStyle = "rgba(0,0,0, 0.6)";
|
|
ctx.fill();
|
|
var num = curveT.toFixed(decimal_places);
|
|
ctx.font = "normal 10px Arial";
|
|
ctx.textAlign = "left";
|
|
ctx.fillText(num, screenWidth - 78, ty);
|
|
}
|
|
|
|
function ptInTControl() {
|
|
var e = window.event;
|
|
var tgt = e.target || e.srcElement;
|
|
var left = tgt.offsetLeft;
|
|
var top = tgt.offsetTop;
|
|
var x = (e.clientX - left);
|
|
var y = (e.clientY - top);
|
|
if (x < screenWidth - 80 || x > screenWidth - 50) {
|
|
return false;
|
|
}
|
|
if (y < 40 || y > screenHeight - 80) {
|
|
return false;
|
|
}
|
|
curveT = (y - 40) / (screenHeight - 120);
|
|
if (curveT < 0 || curveT > 1) {
|
|
throw "stop execution";
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function drawTop() {
|
|
init(tests[testIndex]);
|
|
redraw();
|
|
}
|
|
|
|
function redraw() {
|
|
if (focus_on_selection > 0) {
|
|
var focusXmin = focusYmin = Infinity;
|
|
var focusXmax = focusYmax = -Infinity;
|
|
var choice = 0;
|
|
for (var curves in tests[testIndex]) {
|
|
if (++choice != focus_on_selection) {
|
|
continue;
|
|
}
|
|
var curve = tests[testIndex][curves];
|
|
var last = curve.length;
|
|
for (var idx = 0; idx < last; idx += 2) {
|
|
focusXmin = Math.min(focusXmin, curve[idx]);
|
|
focusXmax = Math.max(focusXmax, curve[idx]);
|
|
focusYmin = Math.min(focusYmin, curve[idx + 1]);
|
|
focusYmax = Math.max(focusYmax, curve[idx + 1]);
|
|
}
|
|
}
|
|
focusXmin -= Math.min(1, Math.max(focusXmax - focusXmin, focusYmax - focusYmin));
|
|
if (focusXmin < focusXmax && focusYmin < focusYmax) {
|
|
setScale(focusXmin, focusXmax, focusYmin, focusYmax);
|
|
}
|
|
}
|
|
ctx.beginPath();
|
|
ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
ctx.fillStyle = "white";
|
|
ctx.fill();
|
|
draw(tests[testIndex], testTitles[testIndex]);
|
|
}
|
|
|
|
function doKeyPress(evt) {
|
|
var char = String.fromCharCode(evt.charCode);
|
|
var focusWasOn = false;
|
|
switch (char) {
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
decimal_places = char - '0';
|
|
redraw();
|
|
break;
|
|
case '-':
|
|
focusWasOn = focus_on_selection;
|
|
if (focusWasOn) {
|
|
focus_on_selection = false;
|
|
scale /= 1.2;
|
|
} else {
|
|
scale /= 2;
|
|
}
|
|
calcLeftTop();
|
|
redraw();
|
|
focus_on_selection = focusWasOn;
|
|
break;
|
|
case '=':
|
|
case '+':
|
|
focusWasOn = focus_on_selection;
|
|
if (focusWasOn) {
|
|
focus_on_selection = false;
|
|
scale *= 1.2;
|
|
} else {
|
|
scale *= 2;
|
|
}
|
|
calcLeftTop();
|
|
redraw();
|
|
focus_on_selection = focusWasOn;
|
|
break;
|
|
case 'b':
|
|
draw_cubic_red ^= true;
|
|
redraw();
|
|
break;
|
|
case 'c':
|
|
drawTop();
|
|
break;
|
|
case 'd':
|
|
var test = tests[testIndex];
|
|
var testClone = [];
|
|
for (var curves in test) {
|
|
var c = test[curves];
|
|
var cClone = [];
|
|
for (var index = 0; index < c.length; ++index) {
|
|
cClone.push(c[index]);
|
|
}
|
|
testClone.push(cClone);
|
|
}
|
|
tests.push(testClone);
|
|
testTitles.push(testTitles[testIndex] + " copy");
|
|
testIndex = tests.length - 1;
|
|
redraw();
|
|
break;
|
|
case 'e':
|
|
draw_endpoints = (draw_endpoints + 1) % 3;
|
|
redraw();
|
|
break;
|
|
case 'f':
|
|
draw_derivative ^= true;
|
|
redraw();
|
|
break;
|
|
case 'i':
|
|
draw_ray_intersect = (draw_ray_intersect + 1) % 3;
|
|
redraw();
|
|
break;
|
|
case 'l':
|
|
var test = tests[testIndex];
|
|
console.log("<div id=\"" + testTitles[testIndex] + "\" >");
|
|
for (var curves in test) {
|
|
var c = test[curves];
|
|
var s = "{{";
|
|
for (var i = 0; i < c.length; i += 2) {
|
|
s += "{";
|
|
s += c[i] + "," + c[i + 1];
|
|
s += "}";
|
|
if (i + 2 < c.length) {
|
|
s += ", ";
|
|
}
|
|
}
|
|
console.log(s + "}},");
|
|
}
|
|
console.log("</div>");
|
|
break;
|
|
case 'm':
|
|
draw_midpoint = (draw_midpoint + 1) % 3;
|
|
redraw();
|
|
break;
|
|
case 'N':
|
|
testIndex += 9;
|
|
case 'n':
|
|
testIndex = (testIndex + 1) % tests.length;
|
|
drawTop();
|
|
break;
|
|
case 'o':
|
|
draw_order ^= true;
|
|
redraw();
|
|
break;
|
|
case 'P':
|
|
testIndex -= 9;
|
|
case 'p':
|
|
if (--testIndex < 0)
|
|
testIndex = tests.length - 1;
|
|
drawTop();
|
|
break;
|
|
case 'q':
|
|
draw_quarterpoint = (draw_quarterpoint + 1) % 3;
|
|
redraw();
|
|
break;
|
|
case 'r':
|
|
for (var i = 0; i < testDivs.length; ++i) {
|
|
var title = testDivs[i].id.toString();
|
|
if (title == testTitles[testIndex]) {
|
|
var str = testDivs[i].firstChild.data;
|
|
parse(str, title);
|
|
var original = tests.pop();
|
|
testTitles.pop();
|
|
tests[testIndex] = original;
|
|
break;
|
|
}
|
|
}
|
|
redraw();
|
|
break;
|
|
case 's':
|
|
draw_sortpoint = (draw_sortpoint + 1) % 3;
|
|
redraw();
|
|
break;
|
|
case 't':
|
|
draw_t ^= true;
|
|
redraw();
|
|
break;
|
|
case 'u':
|
|
draw_closest_t ^= true;
|
|
redraw();
|
|
break;
|
|
case 'v':
|
|
draw_tangents = (draw_tangents + 1) % 4;
|
|
redraw();
|
|
break;
|
|
case 'x':
|
|
draw_point_xy ^= true;
|
|
redraw();
|
|
break;
|
|
case 'y':
|
|
draw_mouse_xy ^= true;
|
|
redraw();
|
|
break;
|
|
case '\\':
|
|
retina_scale ^= true;
|
|
drawTop();
|
|
break;
|
|
case '`':
|
|
++focus_on_selection;
|
|
if (focus_on_selection >= tests[testIndex].length) {
|
|
focus_on_selection = 0;
|
|
}
|
|
setScale(xmin, xmax, ymin, ymax);
|
|
redraw();
|
|
break;
|
|
case '.':
|
|
draw_id ^= true;
|
|
redraw();
|
|
break;
|
|
}
|
|
}
|
|
|
|
function doKeyDown(evt) {
|
|
var char = evt.keyCode;
|
|
var preventDefault = false;
|
|
switch (char) {
|
|
case 37: // left arrow
|
|
if (evt.shiftKey) {
|
|
testIndex -= 9;
|
|
}
|
|
if (--testIndex < 0)
|
|
testIndex = tests.length - 1;
|
|
if (evt.ctrlKey) {
|
|
redraw();
|
|
} else {
|
|
drawTop();
|
|
}
|
|
preventDefault = true;
|
|
break;
|
|
case 39: // right arrow
|
|
if (evt.shiftKey) {
|
|
testIndex += 9;
|
|
}
|
|
if (++testIndex >= tests.length)
|
|
testIndex = 0;
|
|
if (evt.ctrlKey) {
|
|
redraw();
|
|
} else {
|
|
drawTop();
|
|
}
|
|
preventDefault = true;
|
|
break;
|
|
}
|
|
if (preventDefault) {
|
|
evt.preventDefault();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function calcXY() {
|
|
var e = window.event;
|
|
var tgt = e.target || e.srcElement;
|
|
var left = tgt.offsetLeft;
|
|
var top = tgt.offsetTop;
|
|
mouseX = (e.clientX - left) / scale + srcLeft;
|
|
mouseY = (e.clientY - top) / scale + srcTop;
|
|
}
|
|
|
|
function calcLeftTop() {
|
|
srcLeft = mouseX - screenWidth / 2 / scale;
|
|
srcTop = mouseY - screenHeight / 2 / scale;
|
|
}
|
|
|
|
function handleMouseClick() {
|
|
if (!draw_t || !ptInTControl()) {
|
|
calcXY();
|
|
} else {
|
|
redraw();
|
|
}
|
|
}
|
|
|
|
function initDown() {
|
|
var test = tests[testIndex];
|
|
var bestDistance = 1000000;
|
|
activePt = -1;
|
|
for (var curves in test) {
|
|
var testCurve = test[curves];
|
|
if (testCurve.length != 4 && testCurve.length != 6 && testCurve.length != 8) {
|
|
continue;
|
|
}
|
|
for (var i = 0; i < testCurve.length; i += 2) {
|
|
var testX = testCurve[i];
|
|
var testY = testCurve[i + 1];
|
|
var dx = testX - mouseX;
|
|
var dy = testY - mouseY;
|
|
var dist = dx * dx + dy * dy;
|
|
if (dist > bestDistance) {
|
|
continue;
|
|
}
|
|
activeCurve = testCurve;
|
|
activePt = i;
|
|
bestDistance = dist;
|
|
}
|
|
}
|
|
if (activePt >= 0) {
|
|
lastX = mouseX;
|
|
lastY = mouseY;
|
|
}
|
|
}
|
|
|
|
function handleMouseOver() {
|
|
calcXY();
|
|
if (draw_mouse_xy) {
|
|
var num = mouseX.toFixed(decimal_places) + ", " + mouseY.toFixed(decimal_places);
|
|
ctx.beginPath();
|
|
ctx.rect(300, 100, num.length * 6, 10);
|
|
ctx.fillStyle = "white";
|
|
ctx.fill();
|
|
ctx.font = "normal 10px Arial";
|
|
ctx.fillStyle = "black";
|
|
ctx.textAlign = "left";
|
|
ctx.fillText(num, 300, 108);
|
|
}
|
|
if (!mouseDown) {
|
|
activePt = -1;
|
|
return;
|
|
}
|
|
if (activePt < 0) {
|
|
initDown();
|
|
return;
|
|
}
|
|
var deltaX = mouseX - lastX;
|
|
var deltaY = mouseY - lastY;
|
|
lastX = mouseX;
|
|
lastY = mouseY;
|
|
if (activePt == 0) {
|
|
var test = tests[testIndex];
|
|
for (var curves in test) {
|
|
var testCurve = test[curves];
|
|
testCurve[0] += deltaX;
|
|
testCurve[1] += deltaY;
|
|
}
|
|
} else {
|
|
activeCurve[activePt] += deltaX;
|
|
activeCurve[activePt + 1] += deltaY;
|
|
}
|
|
redraw();
|
|
}
|
|
|
|
function start() {
|
|
for (var i = 0; i < testDivs.length; ++i) {
|
|
var title = testDivs[i].id.toString();
|
|
var str = testDivs[i].firstChild.data;
|
|
parse(str, title);
|
|
}
|
|
drawTop();
|
|
window.addEventListener('keypress', doKeyPress, true);
|
|
window.addEventListener('keydown', doKeyDown, true);
|
|
window.onresize = function () {
|
|
drawTop();
|
|
}
|
|
}
|
|
|
|
</script>
|
|
</head>
|
|
|
|
<body onLoad="start();">
|
|
|
|
<canvas id="canvas" width="750" height="500"
|
|
onmousedown="mouseDown = true"
|
|
onmouseup="mouseDown = false"
|
|
onmousemove="handleMouseOver()"
|
|
onclick="handleMouseClick()"
|
|
></canvas >
|
|
</body>
|
|
</html> |