Naloga 2: C0 and C1
This commit is contained in:
@@ -13,8 +13,20 @@
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
#menu {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
padding: 8px;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<div id="menu">
|
||||
<button id="add">Dodaj zlepek</button>
|
||||
<button id="remove">Izbriši zlepek</button>
|
||||
<button id="c0">Zagotovi zveznost</button>
|
||||
<button id="c1">Zagotovi gladkost</button>
|
||||
</div>
|
||||
<canvas></canvas>
|
||||
</body>
|
||||
</html>
|
||||
255
naloga_2/main.js
255
naloga_2/main.js
@@ -1,28 +1,91 @@
|
||||
const addButton = document.getElementById('add');
|
||||
const removeButton = document.getElementById('remove');
|
||||
const c0Button = document.getElementById('c0');
|
||||
const c1Button = document.getElementById('c1');
|
||||
const canvas = document.querySelector('canvas');
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
/** @type {number[][]} */
|
||||
const points = [];
|
||||
let adding = false;
|
||||
/** @type {number|null} */
|
||||
let selected = null;
|
||||
let mouseDown = false;
|
||||
/** @type {number|null} */
|
||||
let movePointIndex = null;
|
||||
|
||||
/** @type {number[][][]} */
|
||||
const curves = [];
|
||||
|
||||
addButton.addEventListener('click', () => {
|
||||
adding = true;
|
||||
curves.push([]);
|
||||
selected = curves.length - 1;
|
||||
});
|
||||
|
||||
removeButton.addEventListener('click', () => {
|
||||
if (selected === null) return;
|
||||
curves.splice(selected, 1);
|
||||
selected = null;
|
||||
draw();
|
||||
});
|
||||
|
||||
c0Button.addEventListener('click', () => {
|
||||
if (curves.length <= 1 || selected === null) return;
|
||||
|
||||
let p = getClosestEndPoint(curves[selected][0][0], curves[selected][0][1]);
|
||||
if (p === null) return;
|
||||
let [curveIndex, pointIndex] = p;
|
||||
curves[selected][0] = curves[curveIndex][pointIndex];
|
||||
|
||||
p = getClosestEndPoint(curves[selected][3][0], curves[selected][3][1]);
|
||||
if (p === null) return;
|
||||
[curveIndex, pointIndex] = p;
|
||||
curves[selected][3] = curves[curveIndex][pointIndex];
|
||||
|
||||
draw();
|
||||
});
|
||||
|
||||
c1Button.addEventListener('click', () => {
|
||||
if (curves.length <= 1 || selected === null) return;
|
||||
|
||||
let [curveIndex, pointIndex] = getClosestEndPoint(
|
||||
curves[selected][0][0],
|
||||
curves[selected][0][1],
|
||||
);
|
||||
// Move first point of selected curve to closest found point
|
||||
curves[selected][0] = curves[curveIndex][pointIndex];
|
||||
// Get control point of found point
|
||||
const h1 = curves[curveIndex][pointIndex === 0 ? 1 : 2];
|
||||
// Set control point to location mirrored over the first point
|
||||
curves[selected][1] = [
|
||||
2 * curves[selected][0][0] - h1[0],
|
||||
2 * curves[selected][0][1] - h1[1],
|
||||
];
|
||||
|
||||
[curveIndex, pointIndex] = getClosestEndPoint(
|
||||
curves[selected][3][0],
|
||||
curves[selected][3][1],
|
||||
);
|
||||
curves[selected][3] = curves[curveIndex][pointIndex];
|
||||
const h2 = curves[curveIndex][pointIndex === 0 ? 1 : 2];
|
||||
curves[selected][2] = [
|
||||
2 * curves[selected][3][0] - h2[0],
|
||||
2 * curves[selected][3][1] - h2[1],
|
||||
];
|
||||
|
||||
draw();
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
*/
|
||||
function addPoint(x, y) {
|
||||
if (points.length === 0) {
|
||||
points.push([x, y]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (points.length === 1) {
|
||||
const h1x = lerp(0.25, points[0][0], x);
|
||||
const h1y = lerp(0.25, points[0][1], y);
|
||||
const h2x = lerp(0.75, points[0][0], x);
|
||||
const h2y = lerp(0.75, points[0][1], y);
|
||||
|
||||
points.push([h1x, h1y], [h2x, h2y], [x, y]);
|
||||
return;
|
||||
const curve = curves[curves.length - 1];
|
||||
curve.push([x, y]);
|
||||
if (curve.length === 4) {
|
||||
adding = false;
|
||||
}
|
||||
draw();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,13 +103,14 @@ function lerp(t, a, b) {
|
||||
* @param {number} t
|
||||
* @param {number[]} p array of points
|
||||
*/
|
||||
function curve(t, p) {
|
||||
function lerpCurve(t, p) {
|
||||
if (p.length === 1) return p[0];
|
||||
return lerp(t, curve(t, p.slice(0, -1)), curve(t, p.slice(1)));
|
||||
return lerp(t, lerpCurve(t, p.slice(0, -1)), lerpCurve(t, p.slice(1)));
|
||||
}
|
||||
|
||||
function drawCircles() {
|
||||
for (const [x, y] of points) {
|
||||
if (selected === null) return;
|
||||
for (const [x, y] of curves[selected]) {
|
||||
context.beginPath();
|
||||
context.ellipse(x, y, 10, 10, 0, 0, Math.PI * 2);
|
||||
context.stroke();
|
||||
@@ -54,35 +118,40 @@ function drawCircles() {
|
||||
}
|
||||
|
||||
function drawLines() {
|
||||
for (let i = 0; i < points.length - 3; i += 3) {
|
||||
const p1 = points[i];
|
||||
const p2 = points[i + 1];
|
||||
const p3 = points[i + 2];
|
||||
const p4 = points[i + 3];
|
||||
for (let i = 0; i < curves.length; i++) {
|
||||
const curve = curves[i];
|
||||
for (let j = 0; j < curve.length - 3; j += 3) {
|
||||
const p1 = curve[j];
|
||||
const p2 = curve[j + 1];
|
||||
const p3 = curve[j + 2];
|
||||
const p4 = curve[j + 3];
|
||||
|
||||
context.beginPath();
|
||||
context.beginPath();
|
||||
|
||||
context.moveTo(p1[0], p1[1]);
|
||||
context.lineTo(p2[0], p2[1]);
|
||||
if (selected === i) {
|
||||
context.moveTo(p1[0], p1[1]);
|
||||
context.lineTo(p2[0], p2[1]);
|
||||
|
||||
context.moveTo(p3[0], p3[1]);
|
||||
context.lineTo(p4[0], p4[1]);
|
||||
context.moveTo(p3[0], p3[1]);
|
||||
context.lineTo(p4[0], p4[1]);
|
||||
}
|
||||
|
||||
context.moveTo(p1[0], p1[1]);
|
||||
for (let t = 0; t <= 1; t += 0.01) {
|
||||
const x = curve(t, [p1[0], p2[0], p3[0], p4[0]]);
|
||||
const y = curve(t, [p1[1], p2[1], p3[1], p4[1]]);
|
||||
context.moveTo(p1[0], p1[1]);
|
||||
for (let t = 0; t <= 100; t++) {
|
||||
const tt = t / 100;
|
||||
const x = lerpCurve(tt, [p1[0], p2[0], p3[0], p4[0]]);
|
||||
const y = lerpCurve(tt, [p1[1], p2[1], p3[1], p4[1]]);
|
||||
|
||||
context.lineTo(x, y);
|
||||
context.lineTo(x, y);
|
||||
}
|
||||
|
||||
context.stroke();
|
||||
}
|
||||
|
||||
context.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
function draw() {
|
||||
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||
if (points.length <= 0) return;
|
||||
|
||||
drawCircles();
|
||||
drawLines();
|
||||
@@ -94,10 +163,120 @@ function resizeCanvas() {
|
||||
draw();
|
||||
}
|
||||
|
||||
window.addEventListener('mousedown', (e) => {
|
||||
addPoint(e.pageX, e.pageY);
|
||||
/**
|
||||
* Gets the index of the closest curve to the point
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @returns {number|null}
|
||||
*/
|
||||
function getClosestCurveIndex(x, y) {
|
||||
/** @type {number|null} */
|
||||
let minDist = null;
|
||||
/** @type {number|null} */
|
||||
let minIndex = null;
|
||||
|
||||
for (let i = 0; i < curves.length; i++) {
|
||||
for (const point of curves[i]) {
|
||||
const dist = Math.abs(
|
||||
Math.sqrt((x - point[0]) ** 2 + (y - point[1]) ** 2),
|
||||
);
|
||||
if (!minDist || dist < minDist) {
|
||||
minDist = dist;
|
||||
minIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return minIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the selected curve point under the mouse
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @returns {number|null}
|
||||
*/
|
||||
function getSelectedPoint(x, y) {
|
||||
if (selected === null) return null;
|
||||
for (let i = 0; i < curves[selected].length; i++) {
|
||||
const point = curves[selected][i];
|
||||
const dist = Math.abs(
|
||||
Math.sqrt((x - point[0]) ** 2 + (y - point[1]) ** 2),
|
||||
);
|
||||
if (dist <= 10) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the closest end point
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @returns {number[]|null}
|
||||
*/
|
||||
function getClosestEndPoint(x, y) {
|
||||
/** @type {number|null} */
|
||||
let minDist = null,
|
||||
minCurveIndex = null,
|
||||
minPointIndex = null;
|
||||
|
||||
for (let i = 0; i < curves.length; i++) {
|
||||
if (i === selected) continue;
|
||||
let dist = Math.abs(
|
||||
Math.sqrt((x - curves[i][0][0]) ** 2 + (y - curves[i][0][1]) ** 2),
|
||||
);
|
||||
if (minDist === null || dist < minDist) {
|
||||
minDist = dist;
|
||||
minCurveIndex = i;
|
||||
minPointIndex = 0;
|
||||
}
|
||||
|
||||
dist = Math.abs(
|
||||
Math.sqrt((x - curves[i][3][0]) ** 2 + (y - curves[i][3][1]) ** 2),
|
||||
);
|
||||
if (minDist === null || dist < minDist) {
|
||||
minDist = dist;
|
||||
minCurveIndex = i;
|
||||
minPointIndex = 3;
|
||||
}
|
||||
}
|
||||
|
||||
return [minCurveIndex, minPointIndex];
|
||||
}
|
||||
|
||||
canvas.addEventListener('mousedown', (e) => {
|
||||
mouseDown = true;
|
||||
|
||||
if (adding) {
|
||||
addPoint(e.pageX, e.pageY);
|
||||
return;
|
||||
}
|
||||
|
||||
const point = getSelectedPoint(e.pageX, e.pageY);
|
||||
if (point !== null) {
|
||||
movePointIndex = point;
|
||||
return;
|
||||
}
|
||||
|
||||
const i = getClosestCurveIndex(e.pageX, e.pageY);
|
||||
if (i === null) return;
|
||||
selected = i;
|
||||
draw();
|
||||
});
|
||||
|
||||
canvas.addEventListener('mousemove', (e) => {
|
||||
if (!mouseDown) return;
|
||||
if (movePointIndex === null) return;
|
||||
curves[selected][movePointIndex] = [e.pageX, e.pageY];
|
||||
draw();
|
||||
});
|
||||
|
||||
canvas.addEventListener('mouseup', () => {
|
||||
mouseDown = false;
|
||||
movePointIndex = null;
|
||||
});
|
||||
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
resizeCanvas();
|
||||
|
||||
Reference in New Issue
Block a user