port from perforce
This commit is contained in:
793
ruins64k/tools/PathHelper.html
Normal file
793
ruins64k/tools/PathHelper.html
Normal file
@@ -0,0 +1,793 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user