blob: 32f49a74469550f082357b46bc506699fabe2c48 [file] [log] [blame]
<html>
<head>
<div style="height:0">
<div id="cubic1">
{{3.13,2.74}, {1.08,4.62}, {3.71,0.94}, {2.01,3.81}}
{{6.71,3.14}, {7.99,2.75}, {8.27,1.96}, {6.35,3.57}}
{{9.45,10.67}, {10.05,5.78}, {13.95,7.46}, {14.72,5.29}}
{{3.34,8.98}, {1.95,10.27}, {3.76,7.65}, {4.96,10.64}}
</div>
</div>
<script type="text/javascript">
var testDivs = [
cubic1,
];
var scale, columns, rows, xStart, yStart;
var ticks = 10;
var at_x = 13 + 0.5;
var at_y = 23 + 0.5;
var decimal_places = 3;
var tests = [];
var testTitles = [];
var testIndex = 0;
var ctx;
var minScale = 1;
var subscale = 1;
var curveT = -1;
var xmin, xmax, ymin, ymax;
var mouseX, mouseY;
var mouseDown = false;
var draw_deriviatives = false;
var draw_endpoints = true;
var draw_hodo = false;
var draw_hodo2 = false;
var draw_hodo_origin = true;
var draw_midpoint = false;
var draw_tangents = true;
var draw_sequence = true;
function parse(test, title) {
var curveStrs = test.split("{{");
if (curveStrs.length == 1)
curveStrs = test.split("=(");
var pattern = /[a-z$=]?-?\d+\.*\d*e?-?\d*/g;
var curves = [];
for (var c in curveStrs) {
var curveStr = curveStrs[c];
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 (curves.length >= 1) {
tests.push(curves);
testTitles.push(title);
}
}
function init(test) {
var canvas = document.getElementById('canvas');
if (!canvas.getContext) return;
canvas.width = window.innerWidth - 20;
canvas.height = window.innerHeight - 20;
ctx = canvas.getContext('2d');
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 -= 1;
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;
}
calcFromScale();
}
function hodograph(cubic) {
var hodo = [];
hodo[0] = 3 * (cubic[2] - cubic[0]);
hodo[1] = 3 * (cubic[3] - cubic[1]);
hodo[2] = 3 * (cubic[4] - cubic[2]);
hodo[3] = 3 * (cubic[5] - cubic[3]);
hodo[4] = 3 * (cubic[6] - cubic[4]);
hodo[5] = 3 * (cubic[7] - cubic[5]);
return hodo;
}
function hodograph2(cubic) {
var quad = hodograph(cubic);
var hodo = [];
hodo[0] = 2 * (quad[2] - quad[0]);
hodo[1] = 2 * (quad[3] - quad[1]);
hodo[2] = 2 * (quad[4] - quad[2]);
hodo[3] = 2 * (quad[5] - quad[3]);
return hodo;
}
function quadraticRootsReal(A, B, C, s) {
if (A == 0) {
if (B == 0) {
s[0] = 0;
return C == 0;
}
s[0] = -C / B;
return 1;
}
/* normal form: x^2 + px + q = 0 */
var p = B / (2 * A);
var q = C / A;
var p2 = p * p;
if (p2 < q) {
return 0;
}
var sqrt_D = 0;
if (p2 > q) {
sqrt_D = sqrt(p2 - q);
}
s[0] = sqrt_D - p;
s[1] = -sqrt_D - p;
return 1 + s[0] != s[1];
}
function add_valid_ts(s, realRoots, t) {
var foundRoots = 0;
for (var index = 0; index < realRoots; ++index) {
var tValue = s[index];
if (tValue >= 0 && tValue <= 1) {
for (var idx2 = 0; idx2 < foundRoots; ++idx2) {
if (t[idx2] != tValue) {
t[foundRoots++] = tValue;
}
}
}
}
return foundRoots;
}
function quadraticRootsValidT(a, b, c, t) {
var s = [];
var realRoots = quadraticRootsReal(A, B, C, s);
var foundRoots = add_valid_ts(s, realRoots, t);
return foundRoots != 0;
}
function find_cubic_inflections(cubic, tValues)
{
var Ax = src[2] - src[0];
var Ay = src[3] - src[1];
var Bx = src[4] - 2 * src[2] + src[0];
var By = src[5] - 2 * src[3] + src[1];
var Cx = src[6] + 3 * (src[2] - src[4]) - src[0];
var Cy = src[7] + 3 * (src[3] - src[5]) - src[1];
return quadraticRootsValidT(Bx * Cy - By * Cx, (Ax * Cy - Ay * Cx),
Ax * By - Ay * Bx, tValues);
}
function dx_at_t(cubic, t) {
var one_t = 1 - t;
var a = cubic[0];
var b = cubic[2];
var c = cubic[4];
var d = cubic[6];
return 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
}
function dy_at_t(cubic, t) {
var one_t = 1 - t;
var a = cubic[1];
var b = cubic[3];
var c = cubic[5];
var d = cubic[7];
return 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
}
function x_at_t(cubic, t) {
var one_t = 1 - t;
var one_t2 = one_t * one_t;
var a = one_t2 * one_t;
var b = 3 * one_t2 * t;
var t2 = t * t;
var c = 3 * one_t * t2;
var d = t2 * t;
return a * cubic[0] + b * cubic[2] + c * cubic[4] + d * cubic[6];
}
function y_at_t(cubic, t) {
var one_t = 1 - t;
var one_t2 = one_t * one_t;
var a = one_t2 * one_t;
var b = 3 * one_t2 * t;
var t2 = t * t;
var c = 3 * one_t * t2;
var d = t2 * t;
return a * cubic[1] + b * cubic[3] + c * cubic[5] + d * cubic[7];
}
function calcFromScale() {
xStart = Math.floor(xmin * subscale) / subscale;
yStart = Math.floor(ymin * subscale) / subscale;
var xEnd = Math.ceil(xmin * subscale) / subscale;
var yEnd = Math.ceil(ymin * subscale) / subscale;
var cCelsW = Math.floor(ctx.canvas.width / 10);
var cCelsH = Math.floor(ctx.canvas.height / 10);
var testW = xEnd - xStart;
var testH = yEnd - yStart;
var scaleWH = 1;
while (cCelsW > testW * scaleWH * 10 && cCelsH > testH * scaleWH * 10) {
scaleWH *= 10;
}
while (cCelsW * 10 < testW * scaleWH && cCelsH * 10 < testH * scaleWH) {
scaleWH /= 10;
}
columns = Math.ceil(xmax * subscale) - Math.floor(xmin * subscale) + 1;
rows = Math.ceil(ymax * subscale) - Math.floor(ymin * subscale) + 1;
var hscale = ctx.canvas.width / columns / ticks;
var vscale = ctx.canvas.height / rows / ticks;
minScale = Math.floor(Math.min(hscale, vscale));
scale = minScale * subscale;
}
function drawLine(x1, y1, x2, y2) {
var unit = scale * ticks;
var xoffset = xStart * -unit + at_x;
var yoffset = yStart * -unit + at_y;
ctx.beginPath();
ctx.moveTo(xoffset + x1 * unit, yoffset + y1 * unit);
ctx.lineTo(xoffset + x2 * unit, yoffset + y2 * unit);
ctx.stroke();
}
function drawPoint(px, py) {
var unit = scale * ticks;
var xoffset = xStart * -unit + at_x;
var yoffset = yStart * -unit + at_y;
var _px = px * unit + xoffset;
var _py = py * unit + yoffset;
ctx.beginPath();
ctx.arc(_px, _py, 3, 0, Math.PI*2, true);
ctx.closePath();
ctx.stroke();
}
function drawPointSolid(px, py) {
drawPoint(px, py);
ctx.fillStyle = "rgba(0,0,0, 0.4)";
ctx.fill();
}
function drawLabel(num, px, py) {
ctx.beginPath();
ctx.arc(px, py, 8, 0, Math.PI*2, true);
ctx.closePath();
ctx.strokeStyle = "rgba(0,0,0, 0.4)";
ctx.lineWidth = num == 0 || num == 3 ? 2 : 1;
ctx.stroke();
ctx.fillStyle = "black";
ctx.font = "normal 10px Arial";
// ctx.rotate(0.001);
ctx.fillText(num, px - 2, py + 3);
// ctx.rotate(-0.001);
}
function drawLabelX(ymin, num, loc) {
var unit = scale * ticks;
var xoffset = xStart * -unit + at_x;
var yoffset = yStart * -unit + at_y;
var px = loc * unit + xoffset;
var py = ymin * unit + yoffset - 20;
drawLabel(num, px, py);
}
function drawLabelY(xmin, num, loc) {
var unit = scale * ticks;
var xoffset = xStart * -unit + at_x;
var yoffset = yStart * -unit + at_y;
var px = xmin * unit + xoffset - 20;
var py = loc * unit + yoffset;
drawLabel(num, px, py);
}
function drawHodoOrigin(hx, hy, hMinX, hMinY, hMaxX, hMaxY) {
ctx.beginPath();
ctx.moveTo(hx, hy - 100);
ctx.lineTo(hx, hy);
ctx.strokeStyle = hMinY < 0 ? "green" : "blue";
ctx.stroke();
ctx.beginPath();
ctx.moveTo(hx, hy);
ctx.lineTo(hx, hy + 100);
ctx.strokeStyle = hMaxY > 0 ? "green" : "blue";
ctx.stroke();
ctx.beginPath();
ctx.moveTo(hx - 100, hy);
ctx.lineTo(hx, hy);
ctx.strokeStyle = hMinX < 0 ? "green" : "blue";
ctx.stroke();
ctx.beginPath();
ctx.moveTo(hx, hy);
ctx.lineTo(hx + 100, hy);
ctx.strokeStyle = hMaxX > 0 ? "green" : "blue";
ctx.stroke();
}
function logCurves(test) {
for (curves in test) {
var curve = test[curves];
if (curve.length != 8) {
continue;
}
var str = "{{";
for (i = 0; i < 8; i += 2) {
str += curve[i].toFixed(2) + "," + curve[i + 1].toFixed(2);
if (i < 6) {
str += "}, {";
}
}
str += "}}";
console.log(str);
}
}
function scalexy(x, y, mag) {
var length = Math.sqrt(x * x + y * y);
return mag / length;
}
function drawArrow(x, y, dx, dy) {
var unit = scale * ticks;
var xoffset = xStart * -unit + at_x;
var yoffset = yStart * -unit + at_y;
var dscale = scalexy(dx, dy, 1);
dx *= dscale;
dy *= dscale;
ctx.beginPath();
ctx.moveTo(xoffset + x * unit, yoffset + y * unit);
x += dx;
y += dy;
ctx.lineTo(xoffset + x * unit, yoffset + y * unit);
dx /= 10;
dy /= 10;
ctx.lineTo(xoffset + (x - dy) * unit, yoffset + (y + dx) * unit);
ctx.lineTo(xoffset + (x + dx * 2) * unit, yoffset + (y + dy * 2) * unit);
ctx.lineTo(xoffset + (x + dy) * unit, yoffset + (y - dx) * unit);
ctx.lineTo(xoffset + x * unit, yoffset + y * unit);
ctx.strokeStyle = "rgba(0,75,0, 0.4)";
ctx.stroke();
}
function draw(test, title) {
ctx.fillStyle = "rgba(0,0,0, 0.1)";
ctx.font = "normal 50px Arial";
ctx.fillText(title, 50, 50);
ctx.font = "normal 10px Arial";
var unit = scale * ticks;
// ctx.lineWidth = "1.001"; "0.999";
var xoffset = xStart * -unit + at_x;
var yoffset = yStart * -unit + at_y;
for (curves in test) {
var curve = test[curves];
if (curve.length != 8) {
continue;
}
ctx.lineWidth = 1;
if (draw_tangents) {
ctx.strokeStyle = "rgba(0,0,255, 0.3)";
drawLine(curve[0], curve[1], curve[2], curve[3]);
drawLine(curve[2], curve[3], curve[4], curve[5]);
drawLine(curve[4], curve[5], curve[6], curve[7]);
}
if (draw_deriviatives) {
var dx = dx_at_t(curve, 0);
var dy = dy_at_t(curve, 0);
drawArrow(curve[0], curve[1], dx, dy);
dx = dx_at_t(curve, 1);
dy = dy_at_t(curve, 1);
drawArrow(curve[6], curve[7], dx, dy);
if (draw_midpoint) {
var midX = x_at_t(curve, 0.5);
var midY = y_at_t(curve, 0.5);
dx = dx_at_t(curve, 0.5);
dy = dy_at_t(curve, 0.5);
drawArrow(midX, midY, dx, dy);
}
}
ctx.beginPath();
ctx.moveTo(xoffset + curve[0] * unit, yoffset + curve[1] * unit);
ctx.bezierCurveTo(
xoffset + curve[2] * unit, yoffset + curve[3] * unit,
xoffset + curve[4] * unit, yoffset + curve[5] * unit,
xoffset + curve[6] * unit, yoffset + curve[7] * unit);
ctx.strokeStyle = "black";
ctx.stroke();
if (draw_endpoints) {
drawPoint(curve[0], curve[1]);
drawPoint(curve[2], curve[3]);
drawPoint(curve[4], curve[5]);
drawPoint(curve[6], curve[7]);
}
if (draw_midpoint) {
var midX = x_at_t(curve, 0.5);
var midY = y_at_t(curve, 0.5);
drawPointSolid(midX, midY);
}
if (draw_hodo) {
var hodo = hodograph(curve);
var hMinX = Math.min(0, hodo[0], hodo[2], hodo[4]);
var hMinY = Math.min(0, hodo[1], hodo[3], hodo[5]);
var hMaxX = Math.max(0, hodo[0], hodo[2], hodo[4]);
var hMaxY = Math.max(0, hodo[1], hodo[3], hodo[5]);
var hScaleX = hMaxX - hMinX > 0 ? ctx.canvas.width / (hMaxX - hMinX) : 1;
var hScaleY = hMaxY - hMinY > 0 ? ctx.canvas.height / (hMaxY - hMinY) : 1;
var hUnit = Math.min(hScaleX, hScaleY);
hUnit /= 2;
var hx = xoffset - hMinX * hUnit;
var hy = yoffset - hMinY * hUnit;
ctx.moveTo(hx + hodo[0] * hUnit, hy + hodo[1] * hUnit);
ctx.quadraticCurveTo(
hx + hodo[2] * hUnit, hy + hodo[3] * hUnit,
hx + hodo[4] * hUnit, hy + hodo[5] * hUnit);
ctx.strokeStyle = "red";
ctx.stroke();
if (draw_hodo_origin) {
drawHodoOrigin(hx, hy, hMinX, hMinY, hMaxX, hMaxY);
}
}
if (draw_hodo2) {
var hodo = hodograph2(curve);
var hMinX = Math.min(0, hodo[0], hodo[2]);
var hMinY = Math.min(0, hodo[1], hodo[3]);
var hMaxX = Math.max(0, hodo[0], hodo[2]);
var hMaxY = Math.max(0, hodo[1], hodo[3]);
var hScaleX = hMaxX - hMinX > 0 ? ctx.canvas.width / (hMaxX - hMinX) : 1;
var hScaleY = hMaxY - hMinY > 0 ? ctx.canvas.height / (hMaxY - hMinY) : 1;
var hUnit = Math.min(hScaleX, hScaleY);
hUnit /= 2;
var hx = xoffset - hMinX * hUnit;
var hy = yoffset - hMinY * hUnit;
ctx.moveTo(hx + hodo[0] * hUnit, hy + hodo[1] * hUnit);
ctx.lineTo(hx + hodo[2] * hUnit, hy + hodo[3] * hUnit);
ctx.strokeStyle = "red";
ctx.stroke();
drawHodoOrigin(hx, hy, hMinX, hMinY, hMaxX, hMaxY);
}
if (draw_sequence) {
var ymin = Math.min(curve[1], curve[3], curve[5], curve[7]);
for (var i = 0; i < 8; i+= 2) {
drawLabelX(ymin, i >> 1, curve[i]);
}
var xmin = Math.min(curve[0], curve[2], curve[4], curve[6]);
for (var i = 1; i < 8; i+= 2) {
drawLabelY(xmin, i >> 1, curve[i]);
}
}
}
}
function drawTop() {
init(tests[testIndex]);
redraw();
}
function redraw() {
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);
switch (char) {
case '2':
draw_hodo2 ^= true;
redraw();
break;
case 'd':
draw_deriviatives ^= true;
redraw();
break;
case 'e':
draw_endpoints ^= true;
redraw();
break;
case 'h':
draw_hodo ^= true;
redraw();
break;
case 'N':
testIndex += 9;
case 'n':
if (++testIndex >= tests.length)
testIndex = 0;
drawTop();
break;
case 'l':
logCurves(tests[testIndex]);
break;
case 'm':
draw_midpoint ^= true;
redraw();
break;
case 'o':
draw_hodo_origin ^= true;
redraw();
break;
case 'P':
testIndex -= 9;
case 'p':
if (--testIndex < 0)
testIndex = tests.length - 1;
drawTop();
break;
case 's':
draw_sequence ^= true;
redraw();
break;
case 't':
draw_tangents ^= true;
redraw();
break;
}
}
function calcXY() {
var e = window.event;
var tgt = e.target || e.srcElement;
var left = tgt.offsetLeft;
var top = tgt.offsetTop;
var unit = scale * ticks;
mouseX = (e.clientX - left - Math.ceil(at_x) + 1) / unit + xStart;
mouseY = (e.clientY - top - Math.ceil(at_y)) / unit + yStart;
}
var lastX, lastY;
var activeCurve = [];
var activePt;
function handleMouseClick() {
calcXY();
}
function initDown() {
var unit = scale * ticks;
var xoffset = xStart * -unit + at_x;
var yoffset = yStart * -unit + at_y;
var test = tests[testIndex];
var bestDistance = 1000000;
activePt = -1;
for (curves in test) {
var testCurve = test[curves];
if (testCurve.length != 8) {
continue;
}
for (var i = 0; i < 8; 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() {
if (!mouseDown) {
activePt = -1;
return;
}
calcXY();
if (activePt < 0) {
initDown();
return;
}
var unit = scale * ticks;
var deltaX = (mouseX - lastX) /* / unit */;
var deltaY = (mouseY - lastY) /*/ unit */;
lastX = mouseX;
lastY = mouseY;
activeCurve[activePt] += deltaX;
activeCurve[activePt + 1] += deltaY;
redraw();
}
function start() {
for (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.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>