【SvelteでHTMLアプリ開発】テオ・ヤンセン機構の多足歩行をシミュレートしてみる


※ 当ページには【広告/PR】を含む場合があります。
2022/05/15
合同会社タコスキングダム|TacosKingdom,LLC.

前回は、
「テオ・ヤンセン機構」の基礎として、仕組みの最小単位となる一脚だけをSvelteアプリとして再現する手順を紹介しました。

合同会社タコスキングダム|タコキンのPスクール
【SvelteでHTMLアプリ開発】テオ・ヤンセン機構をSvelteアプリで再現してみる

「テオ・ヤンセン機構」を簡単に視覚化するためSvelteでHTMLアプリを作って遊んでみます。

一脚だけだと、テオ・ヤンセン機構を持つ機械が全体的にどのように動いているのか把握できないので、歩行するシミュレーターもSvelteで作成してみます。


合同会社タコスキングダム|タコキンのPスクール【Html&Cssアプリ】Html/JavascriptとSassを使った四択クイズゲームの作り方

テオ・ヤンセン機構(6脚版)のシミュレーション

テオ・ヤンセン機構の歩行機械を安定的に歩行できるようにするためには、最低6脚程度は欲しいのかも知れません。

前回では、主に一脚がどのように動くのかに着目してSvelteアプリで実装するところまでをお見せしていました。

今回は前回のSvelteアプリの実装を6回分繰り返すことで、以下のように歩行状態を再現するようにします。

まずはシミュレーション結果を示します。

Svelteの実装は次節にソースコードをそのまま掲載しております。

今回のプロジェクトで、
Svelteのソースコード分割cssの変数を利用した動的な属性値の操作など、サラッと小難しいテクニックも使っていますので、噛みごたえがあるかも知れません。

Svelteプログラミングの学習にご自由にお使いいただくとして、疑問に思うテクニック等がありましたらSvelteのドキュメントなどでご確認ください。


合同会社タコスキングダム|タコキンのPスクール【Html&Cssアプリ】Html/JavascriptとSassを使った四択クイズゲームの作り方

ソースコード

今回のSvelteプロジェクトでは、若干ソースコードが長くなってくるので、メインコードのApp.svelteの他にLegMotion.svelteutil.tsの2つに分離しています。

            
            <script lang="ts">
    import { onMount } from 'svelte';
    import { updatePoints, TPoints, links } from './util';
    import LegMotion from './LegMotion.svelte';

    const period = 2; //👈周期[秒]
    const ticks = 300;
    const timerPeriod = period * 1000 / ticks;
    let timerCount = 0;

    //👇6脚分の座標点セットを定義
    const pointGroup: TPoints[] = [
        {
            O: [0.0, 0.0], A: [0.0, 0.0], B: [-links.a, -links.l], C: [0.0, 0.0],
            D: [0.0, 0.0], E: [0.0, 0.0], F: [0.0, 0.0], G: [0.0, 0.0], sign: 1
        },
        {
            O: [0.0, 0.0], A: [0.0, 0.0], B: [-links.a, -links.l], C: [0.0, 0.0],
            D: [0.0, 0.0], E: [0.0, 0.0], F: [0.0, 0.0], G: [0.0, 0.0], sign: 1
        },
        {
            O: [0.0, 0.0], A: [0.0, 0.0], B: [-links.a, -links.l], C: [0.0, 0.0],
            D: [0.0, 0.0], E: [0.0, 0.0], F: [0.0, 0.0], G: [0.0, 0.0], sign: 1
        },
        {
            O: [0.0, 0.0], A: [0.0, 0.0], B: [-links.a, -links.l], C: [0.0, 0.0],
            D: [0.0, 0.0], E: [0.0, 0.0], F: [0.0, 0.0], G: [0.0, 0.0], sign: -1
        },
        {
            O: [0.0, 0.0], A: [0.0, 0.0], B: [-links.a, -links.l], C: [0.0, 0.0],
            D: [0.0, 0.0], E: [0.0, 0.0], F: [0.0, 0.0], G: [0.0, 0.0], sign: -1
        },
        {
            O: [0.0, 0.0], A: [0.0, 0.0], B: [-links.a, -links.l], C: [0.0, 0.0],
            D: [0.0, 0.0], E: [0.0, 0.0], F: [0.0, 0.0], G: [0.0, 0.0], sign: -1
        }
    ];

    //👇6脚の座標を個別に更新
    $: {
        pointGroup[0] = updatePoints(timerCount/ticks, pointGroup[0]);
        pointGroup[1] = updatePoints(timerCount/ticks + 1/3, pointGroup[1]);
        pointGroup[2] = updatePoints(timerCount/ticks + 2/3, pointGroup[2]);
        pointGroup[3] = updatePoints(timerCount/ticks, pointGroup[3]);
        pointGroup[4] = updatePoints(timerCount/ticks + 1/3, pointGroup[4]);
        pointGroup[5] = updatePoints(timerCount/ticks + 2/3, pointGroup[5]);
    }

    onMount(() => {
        const interval = setInterval(() => {
            timerCount = timerCount > ticks ? 0 : timerCount + 1;
        }, timerPeriod);
        return () => clearInterval(interval);
    });
</script>

//👇各脚をLegMotionコンポーネントとして描画を更新
{#each pointGroup as point, index}
    <LegMotion point={point} index={index}/>
{/each}
        

Svelteでも個別のsvelteファイルとして仕訳することで、単一のコンポーネントとして呼び出すことが出来ます。

LegMotion.svelteはSvelte版のhtmlコンポーネントで、テオ・ヤンセン機構の一脚を描画します。

前回の実装と違うところは、各脚に極性を表す変数(
sign)を用意して、脚の動きを左右方向に柔軟に反転描画できるようにしてあります。

            
            <script lang="ts">
    import type { TPoints, links } from './util';
    const view = {
        w: 600, h: 300,
        t: -50, l: -120, b: 150, r: 480,
    };
    const colors: string[] = ['red','blue','green','red','blue','green'];

    //👇コンポーネントへの属性入力
    export let point: TPoints;
    export let index: number;

    //👇css変数を利用する
    let cssVarStyles = `--leg-color:${colors[index]};`;
</script>

<svg width="{view.w}" height="{view.h}" viewBox='{view.l} {view.t} {view.r} {view.b}' style="{cssVarStyles}">
    <g>
        <path d="
            M 0 0
            L {point.sign*point.A[0]} {-point.A[1]}
            L {point.sign*point.C[0]} {-point.C[1]}
            L {point.sign*point.B[0]} {-point.B[1]}
            L {point.sign*point.E[0]} {-point.E[1]}
            L {point.sign*point.C[0]} {-point.C[1]}
        "/>
        <path d="
            M {point.sign*point.E[0]} {-point.E[1]}
            L {point.sign*point.F[0]} {-point.F[1]}
            L {point.sign*point.G[0]} {-point.G[1]}
            L {point.sign*point.D[0]} {-point.D[1]}
            L {point.sign*point.F[0]} {-point.F[1]}
        "/>
        <path d="
            M {point.sign*point.A[0]} {-point.A[1]}
            L {point.sign*point.D[0]} {-point.D[1]}
            L {point.sign*point.B[0]} {-point.B[1]}
        "/>
    </g>
    <g>
        <circle class="O" cx="0" cy="0" r="2" />
        <circle cx="{point.sign*point.A[0]}" cy="{-point.A[1]}" r="3" />
        <circle cx="{point.sign*point.B[0]}" cy="{-point.B[1]}" r="3" />
        <circle cx="{point.sign*point.C[0]}" cy="{-point.C[1]}" r="3" />
        <circle cx="{point.sign*point.D[0]}" cy="{-point.D[1]}" r="3" />
        <circle cx="{point.sign*point.E[0]}" cy="{-point.E[1]}" r="3" />
        <circle cx="{point.sign*point.F[0]}" cy="{-point.F[1]}" r="3" />
        <circle cx="{point.sign*point.G[0]}" cy="{-point.G[1]}" r="3" />
    </g>
</svg>

<style lang="scss">
    svg {
        position: absolute;
        top: 0;
        left: 0;
        width: 600px;
        height: auto;
        path {
            stroke: var(--leg-color);
            stroke-width: 1.2;
            fill: none;
        }
        circle {
            fill:none;
            stroke:#888888;
            stroke-width:2;
            &.O {
                fill:rgb(118, 118, 49);
                stroke:none;
            }
        }
    }
</style>
        

各座標の更新を行う関数や変数等は各脚で共通ですので、メソッドとして外部モジュール化して
util.tsとしてまとめて定義しておきます。

            
            export const links: any = {
    a: 38, b: 41.5, c: 39.3, d: 40.1, e: 55.8, f: 39.4,
    g: 36.7, h: 65.7, i: 49, j: 50, k: 61.9, l: 7.8,
    m: 15
};

export type TPoints = {
    O: number[], A: number[], B: number[], C: number[],
    D: number[], E: number[], F: number[], G: number[],
    sign: number
}

export function updatePoints(theta: number, prev: TPoints): TPoints {
    let theta_ab: number, theta_bc: number, theta_de: number, theta_df: number;
    let xc: number, yc: number, AB: number, DE: number;
    prev.A = [
        links.m * Math.sin(2*Math.PI*prev.sign*theta),
        links.m * Math.cos(2*Math.PI*prev.sign*theta)
    ];
    prev.B = [-links.a, -links.l];

    AB = Math.sqrt((links.a + prev.A[0])*(links.a + prev.A[0]) + (links.l + prev.A[1])*(links.l + prev.A[1]));
    xc = (AB*AB + links.b*links.b - links.j*links.j) / (2.0*AB);
    yc = Math.sqrt( links.b*links.b - xc*xc );
    theta_ab = Math.atan2(prev.A[1] - prev.B[1], prev.A[0] - prev.B[0]);
    prev.C = [
        -links.a + xc*Math.cos(theta_ab) + yc*Math.cos(theta_ab+Math.PI/2),
        -links.l + xc*Math.sin(theta_ab) + yc*Math.sin(theta_ab+Math.PI/2)
    ];

    xc = (links.b*links.b + links.d*links.d - links.e*links.e) / (2.0*links.b);
    yc = Math.sqrt( links.d*links.d - xc*xc );
    theta_bc = Math.atan2(prev.C[1] - prev.B[1], prev.C[0] - prev.B[0]);
    prev.E = [
        prev.B[0] + xc*Math.cos(theta_bc) + yc*Math.cos(theta_bc+Math.PI/2),
        prev.B[1] + xc*Math.sin(theta_bc) + yc*Math.sin(theta_bc+Math.PI/2)
    ];

    xc = (AB*AB + links.c*links.c - links.k*links.k) / (2.0*AB);
    yc = Math.sqrt( links.c*links.c - xc*xc );
    prev.D = [
        prev.B[0] + xc*Math.cos(theta_ab) + yc*Math.cos(theta_ab-Math.PI/2),
        prev.B[1] + xc*Math.sin(theta_ab) + yc*Math.sin(theta_ab-Math.PI/2)
    ];

    DE = Math.sqrt((prev.D[0] - prev.E[0])*(prev.D[0] - prev.E[0]) + (prev.D[1] - prev.E[1])*(prev.D[1] - prev.E[1]));
    xc = (DE*DE + links.g*links.g - links.f*links.f) / (2.0*DE);
    yc = Math.sqrt( links.g*links.g - xc*xc );
    theta_de = Math.atan2(prev.E[1] - prev.D[1], prev.E[0] - prev.D[0]);
    prev.F = [
        prev.D[0] + xc*Math.cos(theta_de) + yc*Math.cos(theta_de+Math.PI/2),
        prev.D[1] + xc*Math.sin(theta_de) + yc*Math.sin(theta_de+Math.PI/2)
    ];

    xc = (links.g*links.g + links.i*links.i - links.h*links.h) / (2.0*links.g);
    yc = Math.sqrt( links.i*links.i - xc*xc );
    theta_df = Math.atan2(prev.F[1] - prev.D[1], prev.F[0] - prev.D[0]);
    prev.G = [
        prev.D[0] + xc*Math.cos(theta_df) + yc*Math.cos(theta_df+Math.PI/2),
        prev.D[1] + xc*Math.sin(theta_df) + yc*Math.sin(theta_df+Math.PI/2)
    ];
    return prev;
}
        

Svelteアプリ開発には多少SVGやCSSの独特なプログラミングにも慣れる必要がありますが、他のJavascriptフレームワークと比べると、かなり楽にコーディングできるのが強みになっています。
記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

これからの"地方格差"なきプログラミング教育とは何かを考えながら、 地方密着型プログラミング学習関連テーマの記事を不定期で独自にブログ発信しています。

合同会社タコスキングダム|タコキンのPスクール【Html&Cssアプリ】Html/JavascriptとSassを使った四択クイズゲームの作り方