PSVG - Programmable SVGで遊んでみる(その1)

先日 Twitter を見ていたら、PSVG という github のリポジトリに注目されていました。
(おそらく一部界隈の話ではあるけど。)

今回はこちらを使って遊んでいきます。

目次

参考

PSVG とはなんだ

README曰く、以下のように解説されています。

PSVG は、変数・関数・制御フローなどのプログラミング言語機能が導入された SVG の拡張である。

(雑な意訳です。)

サンプルには、フラクタル図形など数学的な表現ができるものや回転物体など物体の各点の座標を計算で表現できるものを取り扱っていたり、
ランダムに動作した軌跡を描いたものがあります。

正直に言うと、サンプル見るのが一番手っ取り早い。

導入

以下の手順で導入します。

1
2
3
4
5
# 適当なディレクトリで
npm install @lingdong/psvg

# src.psvgからdist.svgを作るときは以下のコマンド 拡張子自体はsvgでもOK
npx psvg src.psvg > dist.svg

試しに作ってみる

試しに、自然落下と反発の動きを作ってみます。

bound.psvg
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<psvg width="500" height="500">
<def-fall_bound initial_y="0" r="1" step="100">
<var tmp_pos_y="{ initial_y }" />
<var points_y="{ CAT(initial_y, ';') }" />
<var g="9.8" />
<var sp="0" />
<var e="0.7" />

<for i="0" true="{ i < step }" step="1">
<asgn sp="{ sp + g }" />
<if true="{ tmp_pos_y + r > HEIGHT && sp > 0 }">
<asgn sp="{ (sp * e) * (-1) }" />
</if>
<if>
<cond true="{ tmp_pos_y + r > HEIGHT }">
<asgn tmp_pos_y="{ HEIGHT - r }" />
</cond>
<cond>
<asgn tmp_pos_y="{ tmp_pos_y + sp }" />
</cond>
</if>
<asgn points_y="{ CAT(points_y, tmp_pos_y, ';') }" />
</for>

<return value="{points_y}" />
</def-fall_bound>

<rect x="0" y="0" width="{ WIDTH }" height="{ HEIGHT }" fill="#000" />
<circle cx="{ WIDTH/2 }" cy="0" r="10" stroke="rgb(200,0,0)" fill="rgb(255,0,0)">
<animate attributeName="cy" values="{ fall_bound(10, 10, 200) }" dur="20s" repeatCount="indefinite" />
</circle>
</psvg>

動きをそれっぽくするために、条件式が入ってますが大体こんな感じです。

こちらから SVG を作ると次のようになります。

dist.svg
1
2
3
4
5
6
7
8
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
<rect x="0" y="0" width="500" height="500" fill="#000" />
<circle cx="250" cy="0" r="10" stroke="rgb(200,0,0)" fill="rgb(255,0,0)">
<animate attributeName="cy"
values="10 ; 19.8 ; 39.400000000000006 ; 68.80000000000001 ; 108.00000000000001 ; 157 ; 数字が並ぶが省略"
dur="20s" repeatCount="indefinite" />
</circle>
</svg>

ブラウザで開く次のように表示されます。

自然落下のアニメーションができました。
(最期に動きが落ち着かないのは多少気に入らないですが。)

たくさん動かしてみる

今度は、たくさんの箱をランダムに動かすものを作りました。

randombox.psvg
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
<psvg width="500" height="500">

<var move_box_widht="{ WIDTH * 0.05 }" />
<var move_box_height="{ HEIGHT * 0.05 }" />

<var move_box_pos_x="0" />
<var move_box_pos_y="0" />

<var boxes_number="100" />

<def-positionsrandom step="10" speed="10">
<var block="{ (WIDTH - move_box_widht) / move_box_widht }" />
<var arr_x="" />
<var arr_y="" />
<var tmp_pos_x="{ move_box_widht * ROUND(RANDOM() * block) }" />
<var tmp_pos_y="{ move_box_height * ROUND(RANDOM() * block) }" />

<for i="0" true="{ i<step }" step="1">
<var dir="{ RANDOM() }" />
<var tmp="{ RANDOM() }" />

<if>
<cond true="{ dir > 0.5 }">
<if>
<cond true="{ tmp > 0.5 }">
<asgn tmp_pos_x="{ tmp_pos_x + move_box_widht }" />
</cond>
<cond>
<asgn tmp_pos_x="{ tmp_pos_x - move_box_widht }" />
</cond>
</if>
</cond>
<cond>
<if>
<cond true="{ tmp > 0.5 }">
<asgn tmp_pos_y="{ tmp_pos_y + move_box_height }" />
</cond>
<cond>
<asgn tmp_pos_y="{ tmp_pos_y - move_box_height }" />
</cond>
</if>
</cond>
</if>

<if>
<cond true="{ tmp_pos_x < 0 }">
<asgn tmp_pos_x="0" />
</cond>
<cond true="{ tmp_pos_x > WIDTH - move_box_widht }">
<asgn tmp_pos_x="{ WIDTH - move_box_widht }" />
</cond>
</if>
<if>
<cond true="{ tmp_pos_y<0 }">
<asgn tmp_pos_y="0" />
</cond>
<cond true="{ tmp_pos_y>WIDTH-move_box_height }">
<asgn tmp_pos_y="{ WIDTH-move_box_height }" />
</cond>
</if>

<asgn arr_x="{ CAT(arr_x, tmp_pos_x, ';') }" />
<asgn arr_y="{ CAT(arr_y, tmp_pos_y, ';') }" />
</for>
<animate attributeName="x" values="{ arr_x }" dur="{ speed }s" repeatCount="indefinite" />
<animate attributeName="y" values="{ arr_y }" dur="{ speed }s" repeatCount="indefinite" />
</def-positionsrandom>

<rect x="0" y="0" width="{ WIDTH }" height="{ HEIGHT }" fill="#000" />
<for i="0" true="{ i < boxes_number }" step="1">
<rect x="0" y="0" width="{ move_box_widht }" height="{ move_box_height }"
fill="rgb({ RANDOM() * 155+100 }, { RANDOM() * 155+100 }, { RANDOM() * 155+100 })">
<positionsrandom step="100" speed="30" />
</rect>
</for>
</psvg>

SVG に変換したものを、ブラウザで表示すると以下のようになります。

これが SVG だと初見で気が付く人はあまりいないでしょう。


今回は PSVG で遊んでみました。
今回は、座標点を 1 次元的に表現できるものを描いてみました。
(表現としては 2 次元ですが、座標群が x,y それぞれ個別に 1 次元の配列でした。)

次回は、polygon や polyline など path を使って座標を扱うものに取り組みたいところです。

ではでは。