793 lines
19 KiB
HTML
793 lines
19 KiB
HTML
<html>
|
|
<head>
|
|
<style>
|
|
#pointTemplate {
|
|
display: none;
|
|
}
|
|
|
|
#rootContainer {
|
|
width: 100%;
|
|
height: 100%;
|
|
display: flex;
|
|
}
|
|
|
|
#infoPanel {
|
|
border-right: 1px solid black;
|
|
width: 500px;
|
|
overflow: auto;
|
|
padding-right: 10px;
|
|
}
|
|
|
|
#points {
|
|
max-height: 200px;
|
|
overflow: auto;
|
|
padding: 5px;
|
|
border: 1px solid lightgray;
|
|
}
|
|
|
|
.point {
|
|
display: flex;
|
|
}
|
|
|
|
#canvasContainer {
|
|
flex: 1;
|
|
}
|
|
canvas {
|
|
user-select: none;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
#cppOutput {
|
|
font-family: monospace;
|
|
padding: 10px;
|
|
overflow: auto;
|
|
word-break: keep-all;
|
|
border: 1px solid lightgray;
|
|
width: 100%;
|
|
max-height: 200px;
|
|
margin-bottom: 10px;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
#x, #y {
|
|
flex: 1;
|
|
}
|
|
|
|
#codeContainer {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.simpleGroup {
|
|
display: grid;
|
|
grid-template-columns: 123px auto;
|
|
}
|
|
|
|
textarea {
|
|
height: 100px;
|
|
font-family: monospace;
|
|
padding: 10px;
|
|
border: 1px solid lightgray;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="rootContainer">
|
|
<div id="infoPanel">
|
|
<div class="simpleGroup">
|
|
<label for="isVectorPath">Is VectorPath:</label>
|
|
<input id="isVectorPath" type="checkbox" checked/>
|
|
</div>
|
|
<p></p>
|
|
<div class="simpleGroup">
|
|
<label for="interpolationType">Interpolationtype:</label>
|
|
<select id="interpolationType">
|
|
<option value="Linear">Linear</option>
|
|
<option value="Cosine">Cosine</option>
|
|
<option value="Cubic">Cubic</option>
|
|
<option value="CatmullRom" selected>CatmullRom</option>
|
|
<option value="Hermite">Hermite</option>
|
|
</select>
|
|
</div>
|
|
<div class="simpleGroup">
|
|
<label for="bias">Bias:</label>
|
|
<input disabled id="bias" type="number" min="-10" max="10" value="0" step="0.1"/>
|
|
</div>
|
|
<div class="simpleGroup">
|
|
<label for="tension">Tension:</label>
|
|
<input disabled id="tension" type="number" min="-10" max="10" value="0" step="0.1"/>
|
|
</div>
|
|
<p></p>
|
|
<div class="simpleGroup">
|
|
<label for="steps">Samplingsteps:</label>
|
|
<input id="steps" type="number" value="100" min="0" max="1000"/>
|
|
</div>
|
|
<div class="simpleGroup">
|
|
<label for="markerDistance">Marker distance:</label>
|
|
<input id="markerDistance" type="number" value="0" min="0" max="1000" step="0.1"/>
|
|
</div>
|
|
<p/>
|
|
<div class="simpleGroup">
|
|
<label for="estimatedLength">Estimated length:</label>
|
|
<input id="estimatedLength" readonly type="number"/>
|
|
</div>
|
|
|
|
<p></p>
|
|
<button id="add">+</button>
|
|
<button id="clear">Clear</button>
|
|
<button id="normalize">Normalize</button>
|
|
<div id="points">
|
|
<div id="pointTemplate" class="point">
|
|
<input id="x" type="number" value="0" step="0.1"/>
|
|
<input id="y" type="number" value="0" step="0.1"/>
|
|
<button id="up"> ↥ </button>
|
|
<button id="down"> ↧ </button>
|
|
<button id="delete">X</button>
|
|
</div>
|
|
</div>
|
|
|
|
<p></p>
|
|
<div class="simpleGroup">
|
|
<label for="pointScale">Pointscale:</label>
|
|
<input id="pointScale" type="number" min="0.001" max="10" value="1" step="0.001"/>
|
|
</div>
|
|
<div id="cppOutput"></div>
|
|
<div id="codeContainer">
|
|
<textarea id="code">
|
|
addNewPoint(0, 0);
|
|
addNewPoint(150, 50);
|
|
addNewPoint(200, 100);
|
|
addNewPoint(100, 150);</textarea >
|
|
<button id="run">Run Code</button>
|
|
</div>
|
|
</div>
|
|
<div id="canvasContainer">
|
|
<canvas id="canvas">
|
|
</canvas>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
<script>
|
|
const isVectorPath = document.getElementById("isVectorPath");
|
|
const interpolationType = document.getElementById("interpolationType");
|
|
const steps = document.getElementById("steps");
|
|
const bias = document.getElementById("bias");
|
|
const tension = document.getElementById("tension");
|
|
const pointScale = document.getElementById("pointScale");
|
|
const clearPointsButton = document.getElementById("clear");
|
|
const runCodeButton = document.getElementById("run");
|
|
const codeTextArea = document.getElementById("code");
|
|
const markerDistance = document.getElementById("markerDistance");
|
|
const estimatedLength = document.getElementById("estimatedLength");
|
|
const normalizeButton = document.getElementById("normalize");
|
|
|
|
normalizeButton.onclick = () => {
|
|
const {width, height } = getExtends();
|
|
points.forEach(p => {
|
|
const point = p.get();
|
|
point.x = (point.x / width) * 100;
|
|
point.y = (point.y / height) * 100;
|
|
p.set(point);
|
|
});
|
|
};
|
|
|
|
function getExtends() {
|
|
let minX = Number.MAX_VALUE;
|
|
let maxX = Number.MIN_VALUE;
|
|
let minY = Number.MAX_VALUE;
|
|
let maxY = Number.MIN_VALUE;
|
|
points.forEach(p => {
|
|
const point = p.get();
|
|
minX = Math.min(minX, point.x);
|
|
maxX = Math.max(maxX, point.x);
|
|
|
|
minY = Math.min(minY, point.y);
|
|
maxY = Math.max(maxY, point.y);
|
|
});
|
|
const width = maxX - minX;
|
|
const height = maxY - minY;
|
|
|
|
return {
|
|
minX,
|
|
maxX,
|
|
minY,
|
|
maxY,
|
|
width,
|
|
height
|
|
};
|
|
}
|
|
|
|
runCodeButton.onclick = () => {
|
|
try
|
|
{
|
|
eval(codeTextArea.value);
|
|
}
|
|
catch(e) {
|
|
alert(e.message);
|
|
}
|
|
};
|
|
|
|
clearPointsButton.onclick = () => {
|
|
while (points.length)
|
|
deletePoint(points[0]);
|
|
};
|
|
|
|
isVectorPath.onchange = () => {
|
|
convertToFloatPath();
|
|
updateAll();
|
|
};
|
|
|
|
markerDistance.oninput = tension.oninput = bias.oninput = steps.oninput = updateAll;
|
|
pointScale.oninput = updateCppCode;
|
|
interpolationType.onchange = () => {
|
|
bias.disabled = tension.disabled = interpolationType.value != "Hermite";
|
|
updateAll();
|
|
};
|
|
|
|
const canvas = document.getElementById("canvas");
|
|
const context = canvas.getContext("2d");
|
|
const canvasContainer = document.getElementById("canvasContainer");
|
|
canvas.oncontextmenu = () => false;
|
|
canvas.width = canvasContainer.offsetWidth;
|
|
canvas.height = canvasContainer.offsetHeight;
|
|
let zoom = 1;
|
|
canvas.onwheel = e => {
|
|
zoom *= 1 - e.deltaY/200;
|
|
redraw();
|
|
e.preventDefault()
|
|
};
|
|
let scrollOffset = {x: canvas.width / 2, y: canvas.height / 2};
|
|
|
|
const resizeObserver = new ResizeObserver(() => {
|
|
canvas.width = canvasContainer.offsetWidth;
|
|
canvas.height = canvasContainer.offsetHeight;
|
|
scrollOffset = {x: canvas.width / 2, y: canvas.height / 2};
|
|
redraw();
|
|
});
|
|
resizeObserver.observe(canvasContainer);
|
|
|
|
let clickedPointIndex = -1;
|
|
let isDraggingTheWorld = false;
|
|
let lastMousePosition;
|
|
canvas.onmousedown = (e) => {
|
|
const mousePos = {
|
|
x: e.pageX - canvas.offsetLeft,
|
|
y: e.pageY - canvas.offsetTop
|
|
};
|
|
lastMousePosition = mousePos;
|
|
if (e.which === 1) {
|
|
const worldPoint = fromScreenPoint(mousePos);
|
|
clickedPointIndex = getClickedPointIndex(worldPoint);
|
|
}
|
|
else if (e.which === 3) {
|
|
isDraggingTheWorld = true;
|
|
}
|
|
};
|
|
|
|
canvas.onmouseup = (e) => {
|
|
if (e.which === 1) {
|
|
clickedPointIndex = -1;
|
|
}
|
|
else if (e.which === 3) {
|
|
isDraggingTheWorld = false;
|
|
}
|
|
};
|
|
|
|
canvas.onmousemove = (e) => {
|
|
const mousePos = {
|
|
x: e.pageX - canvas.offsetLeft,
|
|
y: e.pageY - canvas.offsetTop
|
|
};
|
|
|
|
if (clickedPointIndex >= 0) {
|
|
const worldPointLast = fromScreenPoint(lastMousePosition);
|
|
const worldPointNow = fromScreenPoint(mousePos);
|
|
const delta = subPoints(worldPointNow, worldPointLast);
|
|
const oldPos = points[clickedPointIndex].get();
|
|
const newPos = addPoints(oldPos, delta);
|
|
if (!isVectorPath.checked)
|
|
newPos.x = oldPos.x;
|
|
points[clickedPointIndex].set(newPos);
|
|
}
|
|
else if (isDraggingTheWorld) {
|
|
const delta = subPoints(mousePos, lastMousePosition);
|
|
scrollOffset = addPoints(scrollOffset, delta);
|
|
redraw();
|
|
}
|
|
|
|
lastMousePosition = mousePos;
|
|
};
|
|
|
|
const pointTemplate = document.getElementById("pointTemplate");
|
|
const pointsPanel = document.getElementById("points");
|
|
const addButton = document.getElementById("add");
|
|
let points = [];
|
|
addButton.onclick = () => addNewPoint(0, 0);
|
|
addNewPoint(0, 0);
|
|
addNewPoint(150, 50);
|
|
addNewPoint(200, 100);
|
|
addNewPoint(100, 150);
|
|
|
|
function deletePoint(pointData) {
|
|
pointData.dom.remove();
|
|
points = points.filter(item => item !== pointData);
|
|
|
|
convertToFloatPath();
|
|
updateAll();
|
|
}
|
|
|
|
function addNewPoint(x, y) {
|
|
const point = pointTemplate.cloneNode(true);
|
|
point.id = "";
|
|
const deleteButton = point.querySelector("#delete");
|
|
const upButton = point.querySelector("#up");
|
|
const downButton = point.querySelector("#down");
|
|
const xInput = point.querySelector("#x");
|
|
const yInput = point.querySelector("#y");
|
|
|
|
xInput.value = x;
|
|
yInput.value = y;
|
|
xInput.onfocus = () => xInput.select();
|
|
yInput.onfocus = () => yInput.select();
|
|
|
|
let pointData = {
|
|
dom: point,
|
|
get: () => {
|
|
return {
|
|
x: parseFloat(xInput.value),
|
|
y: parseFloat(yInput.value),
|
|
}
|
|
},
|
|
set: (p) => {
|
|
xInput.value = p.x;
|
|
yInput.value = p.y;
|
|
|
|
updateAll();
|
|
}
|
|
}
|
|
|
|
isVectorPath.addEventListener("change", () => {
|
|
xInput.style.display = isVectorPath.checked ? "" : "none";
|
|
});
|
|
|
|
xInput.oninput = yInput.oninput = () => {
|
|
updateAll();
|
|
};
|
|
|
|
deleteButton.onclick = () => {
|
|
deletePoint(pointData);
|
|
};
|
|
|
|
upButton.onclick = () => {
|
|
|
|
const index = points.indexOf(pointData);
|
|
if (index === 0)
|
|
return;
|
|
|
|
point.remove();
|
|
pointsPanel.insertBefore(point, points[index - 1].dom);
|
|
points.splice(index - 1, 0, points.splice(index, 1)[0]);
|
|
|
|
convertToFloatPath();
|
|
updateAll();
|
|
};
|
|
|
|
downButton.onclick = () => {
|
|
|
|
const index = points.indexOf(pointData);
|
|
if (index === points.length - 1)
|
|
return;
|
|
|
|
point.remove();
|
|
points[index + 1].dom.parentNode.insertBefore(point, points[index + 1].dom.nextSibling);
|
|
points.splice(index + 1, 0, points.splice(index, 1)[0]);
|
|
|
|
convertToFloatPath();
|
|
updateAll();
|
|
};
|
|
|
|
points.push(pointData);
|
|
|
|
pointsPanel.appendChild(point);
|
|
|
|
convertToFloatPath();
|
|
updateAll();
|
|
}
|
|
|
|
function convertToFloatPath() {
|
|
if (isVectorPath.checked)
|
|
return;
|
|
|
|
for (let i = 0; i < points.length; ++i) {
|
|
const p = points[i].get();
|
|
p.x = fit(i, 0, points.length - 1, 0, 1) * 100;
|
|
points[i].set(p);
|
|
}
|
|
}
|
|
|
|
function getClickedPointIndex(k) {
|
|
for (let i = 0; i < points.length; ++i) {
|
|
const p = points[i].get();
|
|
/*if (!isVectorPath.checked)
|
|
p.x = fit(i, 0, points.length - 1, 0, 1) * (canvas.width*0.8) - (canvas.width*0.8)/2;*/
|
|
const d = getDistance(k, p);
|
|
|
|
if (d <= 10)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
function updateAll() {
|
|
redraw();
|
|
updateCppCode();
|
|
}
|
|
|
|
function redraw() {
|
|
canvas.width = canvasContainer.offsetWidth;
|
|
canvas.height = canvasContainer.offsetHeight;
|
|
|
|
context.translate(scrollOffset.x, scrollOffset.y);
|
|
context.scale(zoom, -zoom);
|
|
|
|
context.strokeStyle = "#eeeeee";
|
|
for (let i = -100; i < 100; ++i) {
|
|
if (i == 0)
|
|
context.lineWidth = 2/zoom;
|
|
else
|
|
context.lineWidth = 1/zoom;
|
|
|
|
context.beginPath();
|
|
context.moveTo(-100000, i * 10);
|
|
context.lineTo(100000, i * 10);
|
|
context.stroke();
|
|
context.beginPath();
|
|
context.moveTo(i * 10, -100000);
|
|
context.lineTo(i * 10, 100000);
|
|
context.stroke();
|
|
}
|
|
|
|
context.clearRect(0, 0, canvas.width, canvas.heigh);
|
|
context.strokeStyle = "black";
|
|
|
|
if (points.length >= 2) {
|
|
|
|
const stepsValue = parseFloat(steps.value);
|
|
const stepSize = 1.0 / stepsValue;
|
|
|
|
context.beginPath();
|
|
|
|
let lastPosition = getValueAt(0);
|
|
let length = 0;
|
|
for (let step = 0; step <= stepsValue; ++step)
|
|
{
|
|
const p = getValueAt(step * stepSize);
|
|
length += getDistance(lastPosition, p);
|
|
lastPosition = p;
|
|
if (step === 0) {
|
|
context.moveTo(p.x, p.y);
|
|
}
|
|
else {
|
|
context.lineTo(p.x, p.y);
|
|
}
|
|
}
|
|
context.stroke();
|
|
|
|
estimatedLength.value = Number(length.toFixed(4));
|
|
|
|
context.strokeStyle = "green";
|
|
const markerDistanceValue = parseFloat(markerDistance.value);
|
|
if (markerDistanceValue) {
|
|
let currentDistance = markerDistanceValue;
|
|
let lastPosition = getValueAt(0);
|
|
for (let step = 0; step <= stepsValue; ++step)
|
|
{
|
|
const p = getValueAt(step * stepSize);
|
|
const distance = getDistance(lastPosition, p);
|
|
currentDistance += distance;
|
|
if (currentDistance >= markerDistanceValue) {
|
|
currentDistance -= markerDistanceValue;
|
|
|
|
context.beginPath();
|
|
context.arc(p.x, p.y, 3/zoom, 0, 2 * Math.PI);
|
|
context.stroke();
|
|
}
|
|
lastPosition = p;
|
|
}
|
|
context.stroke();
|
|
}
|
|
}
|
|
else {
|
|
estimatedLength.value = 0;
|
|
}
|
|
|
|
context.lineWidth = 2/zoom;
|
|
context.textAlign = "center";
|
|
context.font = `bold ${20/zoom}px OpenSans`;
|
|
context.strokeStyle = "red";
|
|
context.fillStyle = "red";
|
|
|
|
points.forEach(point => {
|
|
const p = point.get();
|
|
context.beginPath();
|
|
context.arc(p.x, p.y, 8/zoom, 0, 2 * Math.PI);
|
|
context.stroke();
|
|
|
|
context.save();
|
|
context.scale(1, -1);
|
|
context.fillText(points.indexOf(point), p.x, -p.y - 12/zoom);
|
|
context.restore();
|
|
});
|
|
}
|
|
|
|
function getCppCode(useVariables) {
|
|
const {width, height } = getExtends();
|
|
const scale = parseFloat(pointScale.value);
|
|
|
|
let text = "";
|
|
|
|
if (useVariables) {
|
|
if (isVectorPath.checked)
|
|
text += `float width = ${printFloat(width * scale)};\n`;
|
|
text += `float height = ${printFloat(height * scale)};\n`;
|
|
}
|
|
|
|
if (isVectorPath.checked)
|
|
text += "VectorPath path;\n";
|
|
else
|
|
text += "FloatPath path;\n";
|
|
|
|
|
|
if (interpolationType.value !== "CatmullRom")
|
|
text += `path.InterpolationType = InterpolationTypes::${interpolationType.value};\n`;
|
|
if (interpolationType.value === "Hermite") {
|
|
text += `path.Bias = ${bias.value};\n`;
|
|
text += `path.Tension = ${tension.value};\n`;
|
|
}
|
|
points.forEach(point => {
|
|
const p = point.get();
|
|
if (useVariables) {
|
|
p.x /= width;
|
|
p.y /= height;
|
|
}
|
|
else {
|
|
p.x *= scale;
|
|
p.y *= scale;
|
|
}
|
|
|
|
if (useVariables) {
|
|
if (isVectorPath.checked)
|
|
text += `path.Values.Add({${printFloat(p.x)} * width, ${printFloat(p.y)} * height});\n`;
|
|
else
|
|
text += `path.Values.Add(${printFloat(p.y)} * height);\n`;
|
|
}
|
|
else {
|
|
if (isVectorPath.checked)
|
|
text += `path.Values.Add({${printFloat(p.x)}, ${printFloat(p.y)}});\n`;
|
|
else
|
|
text += `path.Values.Add(${printFloat(p.y)});\n`;
|
|
}
|
|
});
|
|
|
|
return text;
|
|
|
|
function printFloat(f) {
|
|
f = Number(f.toFixed(4));
|
|
if (f.toString().indexOf(".") == -1)
|
|
return `${f}.0f`;
|
|
else
|
|
return `${f}f`;
|
|
}
|
|
}
|
|
|
|
function updateCppCode() {
|
|
cppOutput.innerText = getCppCode(false) + "\n\n" + getCppCode(true);
|
|
}
|
|
|
|
function getValueAt(mu) {
|
|
let p0, p1, p2, p3;
|
|
let subMu;
|
|
|
|
// GetValuesForInterpolations(t, p0, p1, p2, p3, mu);
|
|
{
|
|
const maxT = points.length;
|
|
const lastI = maxT - 1;
|
|
const i1 = Math.floor(lastI * mu);
|
|
const i2 = i1 + 1;
|
|
const i0 = i1 - 1;
|
|
const i3 = i2 + 1;
|
|
|
|
const deltaMu = 1.0 / lastI;
|
|
const segmentMinMu = Math.floor(lastI * mu) * deltaMu;
|
|
subMu = mu - segmentMinMu;
|
|
subMu = fit(subMu, 0.0, deltaMu, 0.0, 1.0);
|
|
|
|
p0 = i0 < 0 ? addPoints(
|
|
points[0].get(),
|
|
subPoints(
|
|
points[0].get(),
|
|
points[1].get()
|
|
)
|
|
)
|
|
: points[i0].get();
|
|
p1 = points[i1].get();
|
|
p2 = i2 > lastI ? addPoints(
|
|
points[lastI].get(),
|
|
subPoints(
|
|
points[lastI].get(),
|
|
points[lastI - 1].get()
|
|
)
|
|
)
|
|
: points[i2].get();
|
|
p3 = i3 > lastI ? addPoints(
|
|
points[lastI].get(),
|
|
mulPoint(
|
|
subPoints(
|
|
points[lastI].get(),
|
|
points[lastI - 1].get()
|
|
),
|
|
2.0
|
|
)
|
|
)
|
|
: points[i3].get();
|
|
}
|
|
|
|
switch (interpolationType.value) {
|
|
case "CatmullRom":
|
|
return catmullRom2D(p0, p1, p2, p3, subMu);
|
|
case "Hermite":
|
|
return hermite2D(p0, p1, p2, p3, subMu, parseFloat(tension.value), parseFloat(bias.value));
|
|
case "Linear":
|
|
return linear2D(p1, p2, subMu);
|
|
case "Cubic":
|
|
return cubic2D(p0, p1, p2, p3, subMu);
|
|
case "Cosine":
|
|
return cosine2D(p1, p2, subMu);
|
|
}
|
|
}
|
|
|
|
function catmullRom2D(y0, y1, y2, y3, mu)
|
|
{
|
|
return {
|
|
x: catmullRom1D(y0.x, y1.x, y2.x, y3.x, mu),
|
|
y: catmullRom1D(y0.y, y1.y, y2.y, y3.y, mu)
|
|
};
|
|
}
|
|
|
|
function catmullRom1D(y0, y1, y2, y3, mu)
|
|
{
|
|
let a0, a1, a2, a3;
|
|
let mu2;
|
|
|
|
mu2 = mu * mu;
|
|
a0 = y0 * -0.5 + y1 * 1.5 - y2 * 1.5 + y3 * 0.5;
|
|
a1 = y0 - y1 * 2.5 + y2 * 2 - y3 * 0.5;
|
|
a2 = y0 * -0.5 + y2 * 0.5;
|
|
a3 = y1;
|
|
|
|
return a0 * mu * mu2 + a1 * mu2 + a2 * mu + a3;
|
|
}
|
|
|
|
function hermite2D(y0, y1, y2, y3, mu, tension, bias) {
|
|
return {
|
|
x: hermite1D(y0.x, y1.x, y2.x, y3.x, mu, tension, bias),
|
|
y: hermite1D(y0.y, y1.y, y2.y, y3.y, mu, tension, bias)
|
|
};
|
|
}
|
|
|
|
function hermite1D(y0, y1, y2, y3, mu, tension, bias)
|
|
{
|
|
let m0, m1, mu2, mu3;
|
|
let a0, a1, a2, a3;
|
|
|
|
mu2 = mu * mu;
|
|
mu3 = mu2 * mu;
|
|
m0 = (y1 - y0) * (1 + bias) * (1 - tension) / 2;
|
|
m0 += (y2 - y1) * (1 - bias) * (1 - tension) / 2;
|
|
m1 = (y2 - y1) * (1 + bias) * (1 - tension) / 2;
|
|
m1 += (y3 - y2) * (1 - bias) * (1 - tension) / 2;
|
|
a0 = 2 * mu3 - 3 * mu2 + 1;
|
|
a1 = mu3 - 2 * mu2 + mu;
|
|
a2 = mu3 - mu2;
|
|
a3 = -2 * mu3 + 3 * mu2;
|
|
|
|
return a0 * y1 + a1 * m0 + a2 * m1 + a3 * y2;
|
|
}
|
|
|
|
function linear2D(y1, y2, mu)
|
|
{
|
|
return {
|
|
x: linear1D(y1.x, y2.x, mu),
|
|
y: linear1D(y1.y, y2.y, mu),
|
|
};
|
|
}
|
|
|
|
function linear1D(y1, y2, mu)
|
|
{
|
|
return y1 * (1 - mu) + y2 * mu;
|
|
}
|
|
|
|
function cosine2D(y1, y2, mu)
|
|
{
|
|
return {
|
|
x: cosine1D(y1.x, y2.x, mu),
|
|
y: cosine1D(y1.y, y2.y, mu),
|
|
};
|
|
}
|
|
|
|
function cosine1D(y1, y2, mu)
|
|
{
|
|
let mu2;
|
|
|
|
mu2 = (1 - Math.cos(mu * 3.141)) / 2;
|
|
return y1 * (1 - mu2) + y2 * mu2;
|
|
}
|
|
|
|
function cubic2D(y0, y1, y2, y3, mu) {
|
|
return {
|
|
x: cubic1D(y0.x, y1.x, y2.x, y3.x, mu),
|
|
y: cubic1D(y0.y, y1.y, y2.y, y3.y, mu)
|
|
};
|
|
}
|
|
|
|
function cubic1D(y0, y1, y2, y3, mu)
|
|
{
|
|
let a0, a1, a2, a3;
|
|
let mu2;
|
|
|
|
mu2 = mu * mu;
|
|
a0 = y3 - y2 - y0 + y1;
|
|
a1 = y0 - y1 - a0;
|
|
a2 = y2 - y0;
|
|
a3 = y1;
|
|
|
|
return a0 * mu * mu2 + a1 * mu2 + a2 * mu + a3;
|
|
}
|
|
|
|
function fit(value, oldMin, oldMax, newMin, newMax) {
|
|
const percentage = (value - oldMin) / (oldMax - oldMin);
|
|
return (newMax - newMin) * percentage + newMin;
|
|
}
|
|
|
|
function getDistance(a, b) {
|
|
const d = subPoints(a, b);
|
|
return Math.sqrt(d.x*d.x+d.y*d.y);
|
|
}
|
|
|
|
function addPoints(a,b)
|
|
{
|
|
return {
|
|
x: a.x + b.x,
|
|
y: a.y + b.y
|
|
};
|
|
}
|
|
|
|
function subPoints(a,b)
|
|
{
|
|
return {
|
|
x: a.x - b.x,
|
|
y: a.y - b.y
|
|
};
|
|
}
|
|
|
|
function mulPoint(a,b)
|
|
{
|
|
return {
|
|
x: a.x * b,
|
|
y: a.y * b
|
|
};
|
|
}
|
|
|
|
function toScreenPoint(p) {
|
|
const matrix = context.getTransform();
|
|
return matrix.transformPoint(p);
|
|
}
|
|
|
|
function fromScreenPoint(p) {
|
|
const matrix = context.getTransform();
|
|
return matrix.inverse().transformPoint(p);
|
|
}
|
|
|
|
</script>
|
|
</html> |