スプライン系
ChatGPT4o作。
多分大丈夫と思われるが本当に大丈夫かどうかはわからぬ。
検証はこれからなされる。
参考
Bスプライン
%%html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>2D B-Spline Curve</title>
</head>
<body>
<label for="vectorInput">Enter 2D vectors (e.g., [[x1,y1], [x2,y2], [x3,y3]]):</label>
<input type="text" id="vectorInput" size="50" value="[[50,300],[150,150],[250,250],[350,50],[450,300]]">
<button onclick="updateCanvas()">Draw B-Spline</button>
<br><br>
<canvas id="splineCanvas" width="500" height="400" style="border:1px solid #000;"></canvas>
<script>
function bspline(points, degree, t, knots) {
const n = points.length - 1;
function N(i, d, t) {
if (d === 0) {
return (knots[i] <= t && t < knots[i + 1]) ? 1 : 0;
} else {
const w1 = (t - knots[i]) / (knots[i + d] - knots[i]);
const w2 = (knots[i + d + 1] - t) / (knots[i + d + 1] - knots[i + 1]);
const n1 = N(i, d - 1, t);
const n2 = N(i + 1, d - 1, t);
return (isNaN(w1 * n1) ? 0 : w1 * n1) + (isNaN(w2 * n2) ? 0 : w2 * n2);
}
}
let x = 0;
let y = 0;
for (let i = 0; i <= n; i++) {
const coeff = N(i, degree, t);
x += coeff * points[i][0];
y += coeff * points[i][1];
}
return [x, y];
}
function drawBSpline(points, ctx) {
const degree = 3; // Cubic B-Spline
const numSamples = 100;
// Generate knot vector
const knots = [];
for (let i = 0; i < points.length + degree + 1; i++) {
if (i < degree) {
knots.push(0);
} else if (i <= points.length) {
knots.push(i - degree);
} else {
knots.push(points.length - degree + 1);
}
}
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.beginPath();
const [x0, y0] = bspline(points, degree, knots[degree], knots);
ctx.moveTo(x0, y0);
for (let i = 1; i <= numSamples; i++) {
const t = (i * (knots[points.length] - knots[degree])) / numSamples + knots[degree];
const [x, y] = bspline(points, degree, t, knots);
ctx.lineTo(x, y);
}
ctx.strokeStyle = 'blue';
ctx.lineWidth = 2;
ctx.stroke();
}
function updateCanvas() {
const input = document.getElementById('vectorInput').value;
let points;
try {
points = JSON.parse(input);
} catch (e) {
alert('Invalid input format. Please enter a valid JSON array.');
return;
}
if (!Array.isArray(points) || points.some(p => !Array.isArray(p) || p.length !== 2)) {
alert('Invalid input format. Each vector should be a 2-element array.');
return;
}
const canvas = document.getElementById('splineCanvas');
const ctx = canvas.getContext('2d');
drawBSpline(points, ctx);
}
</script>
</body>
</html>
エルミート
%%html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>2D Hermite Spline Curve</title>
</head>
<body>
<label for="vectorInput">Enter 2D vectors (e.g., [[x1,y1], [x2,y2], [x3,y3]]):</label>
<input type="text" id="vectorInput" size="50" value="[[50,300],[150,150],[250,250],[350,50],[450,300]]">
<button onclick="updateCanvas()">Draw Hermite Spline</button>
<br><br>
<canvas id="splineCanvas" width="500" height="400" style="border:1px solid #000;"></canvas>
<script>
function hermiteSpline(points, tangents, numSegments = 16) {
const result = [];
for (let i = 0; i < points.length - 1; i++) {
const p0 = points[i];
const p1 = points[i + 1];
const t0 = tangents[i];
const t1 = tangents[i + 1];
for (let t = 0; t <= numSegments; t++) {
const s = t / numSegments;
const s2 = s * s;
const s3 = s2 * s;
const h00 = 2 * s3 - 3 * s2 + 1;
const h10 = s3 - 2 * s2 + s;
const h01 = -2 * s3 + 3 * s2;
const h11 = s3 - s2;
const x = h00 * p0[0] + h10 * t0[0] + h01 * p1[0] + h11 * t1[0];
const y = h00 * p0[1] + h10 * t0[1] + h01 * p1[1] + h11 * t1[1];
result.push([x, y]);
}
}
return result;
}
function drawHermiteSpline(points, ctx) {
const tangents = [];
for (let i = 0; i < points.length; i++) {
if (i === 0 || i === points.length - 1) {
tangents.push([0, 0]);
} else {
tangents.push([
(points[i + 1][0] - points[i - 1][0]) / 2,
(points[i + 1][1] - points[i - 1][1]) / 2
]);
}
}
const splinePoints = hermiteSpline(points, tangents);
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.beginPath();
ctx.moveTo(splinePoints[0][0], splinePoints[0][1]);
for (let i = 1; i < splinePoints.length; i++) {
ctx.lineTo(splinePoints[i][0], splinePoints[i][1]);
}
ctx.strokeStyle = 'blue';
ctx.lineWidth = 2;
ctx.stroke();
}
function updateCanvas() {
const input = document.getElementById('vectorInput').value;
let points;
try {
points = JSON.parse(input);
} catch (e) {
alert('Invalid input format. Please enter a valid JSON array.');
return;
}
if (!Array.isArray(points) || points.some(p => !Array.isArray(p) || p.length !== 2)) {
alert('Invalid input format. Each vector should be a 2-element array.');
return;
}
const canvas = document.getElementById('splineCanvas');
const ctx = canvas.getContext('2d');
drawHermiteSpline(points, ctx);
}
</script>
</body>
</html>
カーディナル
%%html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>2D Cardinal Spline Curve</title>
</head>
<body>
<label for="vectorInput">Enter 2D vectors (e.g., [[x1,y1], [x2,y2], [x3,y3]]):</label>
<input type="text" id="vectorInput" size="50" value="[[50,300],[150,150],[250,250],[350,50],[450,300]]">
<button onclick="updateCanvas()">Draw Cardinal Spline</button>
<br><br>
<canvas id="splineCanvas" width="500" height="400" style="border:1px solid #000;"></canvas>
<script>
function cardinalSpline(points, tension = 0.5, numSegments = 16) {
const result = [];
for (let i = 0; i < points.length - 1; i++) {
const p0 = i > 0 ? points[i - 1] : points[i];
const p1 = points[i];
const p2 = points[i + 1];
const p3 = i !== points.length - 2 ? points[i + 2] : points[i + 1];
for (let t = 0; t <= numSegments; t++) {
const s = t / numSegments;
const s2 = s * s;
const s3 = s2 * s;
const m1 = [
tension * (p2[0] - p0[0]),
tension * (p2[1] - p0[1])
];
const m2 = [
tension * (p3[0] - p1[0]),
tension * (p3[1] - p1[1])
];
const a = 2 * s3 - 3 * s2 + 1;
const b = s3 - 2 * s2 + s;
const c = s3 - s2;
const d = -2 * s3 + 3 * s2;
const x = a * p1[0] + b * m1[0] + c * m2[0] + d * p2[0];
const y = a * p1[1] + b * m1[1] + c * m2[1] + d * p2[1];
result.push([x, y]);
}
}
return result;
}
function drawCardinalSpline(points, ctx, tension = 0.5, numSegments = 16) {
const splinePoints = cardinalSpline(points, tension, numSegments);
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.beginPath();
ctx.moveTo(splinePoints[0][0], splinePoints[0][1]);
for (let i = 1; i < splinePoints.length; i++) {
ctx.lineTo(splinePoints[i][0], splinePoints[i][1]);
}
ctx.strokeStyle = 'blue';
ctx.lineWidth = 2;
ctx.stroke();
}
function updateCanvas() {
const input = document.getElementById('vectorInput').value;
let points;
try {
points = JSON.parse(input);
} catch (e) {
alert('Invalid input format. Please enter a valid JSON array.');
return;
}
if (!Array.isArray(points) || points.some(p => !Array.isArray(p) || p.length !== 2)) {
alert('Invalid input format. Each vector should be a 2-element array.');
return;
}
const canvas = document.getElementById('splineCanvas');
const ctx = canvas.getContext('2d');
drawCardinalSpline(points, ctx);
}
</script>
</body>
</html>
NURBS
%%html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NURBS Curve</title>
</head>
<body>
<label for="vectorInput">Enter 2D vectors (e.g., [[x1,y1], [x2,y2], [x3,y3]]):</label>
<input type="text" id="vectorInput" size="50" value="[[50,300],[150,150],[250,250],[350,50],[450,300]]">
<label for="weightInput">Enter weights (e.g., [1, 1, 1, 1, 1]):</label>
<input type="text" id="weightInput" size="50" value="[1, 1, 1, 1, 1]">
<button onclick="updateCanvas()">Draw NURBS</button>
<br><br>
<canvas id="splineCanvas" width="500" height="400" style="border:1px solid #000;"></canvas>
<script>
function NURBS(points, weights, degree, numSegments = 100) {
const n = points.length - 1;
const m = n + degree + 1;
const knots = [];
// Generate knot vector
for (let i = 0; i <= m; i++) {
if (i <= degree) {
knots.push(0);
} else if (i >= m - degree) {
knots.push(n - degree + 1);
} else {
knots.push(i - degree);
}
}
//console.log("Knot vector:", knots);
function N(i, k, t) {
if (k === 0) {
return (knots[i] <= t && t < knots[i + 1]) ? 1 : 0;
} else {
const den1 = knots[i + k] - knots[i];
const den2 = knots[i + k + 1] - knots[i + 1];
const a = den1 !== 0 ? (t - knots[i]) / den1 : 0;
const b = den2 !== 0 ? (knots[i + k + 1] - t) / den2 : 0;
const term1 = a * N(i, k - 1, t);
const term2 = b * N(i + 1, k - 1, t);
return term1 + term2;
}
}
const result = [];
for (let s = 0; s <= numSegments; s++) {
const t = (s / numSegments) * (knots[n + 1] - knots[degree]) + knots[degree];
let x = 0;
let y = 0;
let w = 0;
for (let i = 0; i <= n; i++) {
const basis = N(i, degree, t) * weights[i];
x += basis * points[i][0];
y += basis * points[i][1];
w += basis;
}
if (w !== 0) {
result.push([x / w, y / w]);
} else {
result.push([NaN, NaN]); // This should not happen, but we add it to debug
}
}
//console.log("Result points:", result);
return result;
}
function drawNURBS(points, weights, ctx) {
const degree = 3; // Cubic NURBS
const splinePoints = NURBS(points, weights, degree);
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.beginPath();
if (splinePoints.length > 0) {
ctx.moveTo(splinePoints[0][0], splinePoints[0][1]);
for (let i = 1; i < splinePoints.length; i++) {
ctx.lineTo(splinePoints[i][0], splinePoints[i][1]);
}
ctx.strokeStyle = 'blue';
ctx.lineWidth = 2;
ctx.stroke();
}
}
function updateCanvas() {
const pointsInput = document.getElementById('vectorInput').value;
const weightsInput = document.getElementById('weightInput').value;
let points, weights;
try {
points = JSON.parse(pointsInput);
weights = JSON.parse(weightsInput);
} catch (e) {
alert('Invalid input format. Please enter valid JSON arrays.');
return;
}
// console.log("Input points:", points);
// console.log("Input weights:", weights);
if (!Array.isArray(points) || points.some(p => !Array.isArray(p) || p.length !== 2)) {
alert('Invalid input format for points. Each vector should be a 2-element array.');
return;
}
if (!Array.isArray(weights) || weights.length !== points.length) {
alert('Invalid input format for weights. Each weight should correspond to a control point.');
return;
}
const canvas = document.getElementById('splineCanvas');
const ctx = canvas.getContext('2d');
drawNURBS(points, weights, ctx);
}
</script>
</body>
</html>
閉Bスプライン
%%html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Closed B-Spline Curve</title>
</head>
<body>
<label for="vectorInput">Enter 2D vectors (e.g., [[x1,y1], [x2,y2], [x3,y3]]):</label>
<input type="text" id="vectorInput" size="50" value="[[50,300],[150,150],[250,250],[350,50],[450,300]]">
<button onclick="updateCanvas()">Draw Closed B-Spline</button>
<br><br>
<canvas id="splineCanvas" width="500" height="400" style="border:1px solid #000;"></canvas>
<script>
function BSpline(points, degree, numSegments = 100) {
const n = points.length - 1;
const m = n + degree + 1;
const knots = [];
// Generate knot vector for closed B-spline
for (let i = 0; i <= m; i++) {
knots.push(i);
}
console.log("Knot vector:", knots);
function N(i, k, t) {
if (k === 0) {
return (knots[i] <= t && t < knots[i + 1]) ? 1 : 0;
} else {
const den1 = knots[i + k] - knots[i];
const den2 = knots[i + k + 1] - knots[i + 1];
const a = den1 !== 0 ? (t - knots[i]) / den1 : 0;
const b = den2 !== 0 ? (knots[i + k + 1] - t) / den2 : 0;
const term1 = a * N(i, k - 1, t);
const term2 = b * N(i + 1, k - 1, t);
return term1 + term2;
}
}
const result = [];
for (let s = 0; s <= numSegments; s++) {
const t = (s / numSegments) * (knots[n + 1] - knots[degree]) + knots[degree];
let x = 0;
let y = 0;
for (let i = 0; i <= n; i++) {
const basis = N(i, degree, t);
x += basis * points[i % points.length][0];
y += basis * points[i % points.length][1];
}
result.push([x, y]);
}
console.log("Result points:", result);
return result;
}
function drawClosedBSpline(points, ctx) {
const degree = 3; // Cubic B-Spline
const extendedPoints = points.concat(points.slice(0, degree));
const splinePoints = BSpline(extendedPoints, degree);
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.beginPath();
if (splinePoints.length > 0) {
ctx.moveTo(splinePoints[0][0], splinePoints[0][1]);
for (let i = 1; i < splinePoints.length - degree; i++) {
ctx.lineTo(splinePoints[i][0], splinePoints[i][1]);
}
ctx.closePath();
ctx.strokeStyle = 'blue';
ctx.lineWidth = 2;
ctx.stroke();
}
}
function updateCanvas() {
const pointsInput = document.getElementById('vectorInput').value;
let points;
try {
points = JSON.parse(pointsInput);
} catch (e) {
alert('Invalid input format. Please enter a valid JSON array.');
return;
}
console.log("Input points:", points);
if (!Array.isArray(points) || points.some(p => !Array.isArray(p) || p.length !== 2)) {
alert('Invalid input format for points. Each vector should be a 2-element array.');
return;
}
const canvas = document.getElementById('splineCanvas');
const ctx = canvas.getContext('2d');
drawClosedBSpline(points, ctx);
}
</script>
</body>
</html>
閉NURBS
%%html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Closed NURBS Curve</title>
<style>
canvas {
border: 1px solid black;
}
#inputArea {
margin-bottom: 10px;
}
</style>
</head>
<body>
<div id="inputArea">
<textarea id="matrixInput" rows="10" cols="50">[[50,300],[150,150],[250,250],[350,50],[450,300]]</textarea><br>
<button onclick="updateCanvas()">Draw Closed NURBS</button>
</div>
<canvas id="canvas" width="800" height="800"></canvas>
<script>
class Vec {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
function NURBS(points, weights, degree, numSegments = 100) {
const n = points.length - 1;
const m = n + degree + 1;
const knots = [];
// Generate knot vector for closed NURBS
for (let i = 0; i <= m; i++) {
knots.push(i);
}
console.log("Knot vector:", knots);
function N(i, k, t) {
if (k === 0) {
return (knots[i] <= t && t < knots[i + 1]) ? 1 : 0;
} else {
const den1 = knots[i + k] - knots[i];
const den2 = knots[i + k + 1] - knots[i + 1];
const a = den1 !== 0 ? (t - knots[i]) / den1 : 0;
const b = den2 !== 0 ? (knots[i + k + 1] - t) / den2 : 0;
const term1 = a * N(i, k - 1, t);
const term2 = b * N(i + 1, k - 1, t);
return term1 + term2;
}
}
const result = [];
for (let s = 0; s <= numSegments; s++) {
const t = (s / numSegments) * (knots[n + 1] - knots[degree]) + knots[degree];
let x = 0;
let y = 0;
let w = 0;
for (let i = 0; i <= n; i++) {
const basis = N(i, degree, t) * weights[i];
x += basis * points[i % points.length][0];
y += basis * points[i % points.length][1];
w += basis;
}
result.push([x / w, y / w]);
}
console.log("Result points:", result);
return result;
}
function drawClosedNURBS(points, weights, ctx) {
const degree = 3; // Cubic NURBS
const extendedPoints = points.concat(points.slice(0, degree));
const extendedWeights = weights.concat(weights.slice(0, degree));
const splinePoints = NURBS(extendedPoints, extendedWeights, degree);
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.beginPath();
if (splinePoints.length > 0) {
ctx.moveTo(splinePoints[0][0], splinePoints[0][1]);
for (let i = 1; i < splinePoints.length; i++) {
ctx.lineTo(splinePoints[i][0], splinePoints[i][1]);
}
ctx.closePath();
ctx.strokeStyle = 'blue';
ctx.lineWidth = 2;
ctx.stroke();
}
}
function updateCanvas() {
const pointsInput = document.getElementById('matrixInput').value;
let points;
try {
points = JSON.parse(pointsInput);
} catch (e) {
alert('Invalid input format. Please enter a valid JSON array.');
return;
}
console.log("Input points:", points);
if (!Array.isArray(points) || points.some(p => !Array.isArray(p) || p.length !== 2)) {
alert('Invalid input format for points. Each vector should be a 2-element array.');
return;
}
const weights = Array(points.length).fill(1); // Assign equal weights for simplicity
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
drawClosedNURBS(points, weights, ctx);
}
</script>
</body>
</html>
この記事が気に入ったらサポートをしてみませんか?