スプライン系


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>

この記事が気に入ったらサポートをしてみませんか?