port from perforce

This commit is contained in:
2026-04-18 22:31:51 +02:00
commit 8d0ab5b7cc
8409 changed files with 3972376 additions and 0 deletions

View 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"> &#8613 </button>
<button id="down"> &#8615 </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>