├── .gitignore
├── image
└── description.gif
├── css
└── theme.css
├── LICENSE.txt
├── js
├── lib
│ ├── paperjs-v0.12.17
│ │ └── node
│ │ │ ├── xml.js
│ │ │ ├── self.js
│ │ │ ├── canvas.js
│ │ │ └── extend.js
│ ├── matter-attractors.js
│ ├── matter-wrap.js
│ ├── decomp.js
│ └── jquery-3.7.1.slim.min.js
└── simple-svg-physics-runner.js
├── index.html
├── README_ja.md
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | _backup/
2 |
3 | .DS_Store
4 |
5 |
--------------------------------------------------------------------------------
/image/description.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shspage/simple-svg-physics-runner/HEAD/image/description.gif
--------------------------------------------------------------------------------
/css/theme.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | margin: 0;
4 | position:relative;
5 | height: 100%;
6 | -webkit-user-select: none;
7 | }
8 |
9 | canvas
10 | {
11 | background-color:#ccc;
12 | display:block;
13 | }
14 | canvas[resize] {
15 | width: 100%;
16 | height: 96%;
17 | }
18 |
19 | #status_en, #status_ja{
20 | font-size:80%;
21 | }
22 |
23 | #div_canvas{
24 | top: 0px;
25 | position: absolute;
26 | width: 100%;
27 | height: 100%;
28 | }
29 | #div_help{
30 | top: 0px;
31 | left: 20px;
32 | position: absolute;
33 | color:#888;
34 | }
35 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2018 Hiroyuki Sato
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining
5 | a copy of this software and associated documentation files (the "Software"),
6 | to deal in the Software without restriction, including without limitation
7 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 | and/or sell copies of the Software, and to permit persons to whom the Software
9 | is furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/js/lib/paperjs-v0.12.17/node/xml.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
3 | * http://paperjs.org/
4 | *
5 | * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey
6 | * http://juerglehni.com/ & https://puckey.studio/
7 | *
8 | * Distributed under the MIT license. See LICENSE file for details.
9 | *
10 | * All rights reserved.
11 | */
12 |
13 | module.exports = function(self) {
14 | // Define XMLSerializer shim, to emulate browser behavior.
15 | // Effort to bring XMLSerializer to jsdom:
16 | // https://github.com/tmpvar/jsdom/issues/1368
17 | self.XMLSerializer = function XMLSerializer() {
18 | };
19 |
20 | self.XMLSerializer.prototype = {
21 | serializeToString: function(node) {
22 | if (!node)
23 | return '';
24 | // Fix a jsdom issue where all SVG tagNames are lowercased:
25 | // https://github.com/tmpvar/jsdom/issues/620
26 | var text = node.outerHTML,
27 | tagNames = ['linearGradient', 'radialGradient', 'clipPath',
28 | 'textPath'];
29 | for (var i = 0, l = tagNames.length; i < l; i++) {
30 | var tagName = tagNames[i];
31 | text = text.replace(
32 | new RegExp('(<|)' + tagName.toLowerCase() + '\\b', 'g'),
33 | function(match, start) {
34 | return start + tagName;
35 | });
36 | }
37 | return text;
38 | }
39 | };
40 | };
41 |
--------------------------------------------------------------------------------
/js/lib/paperjs-v0.12.17/node/self.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
3 | * http://paperjs.org/
4 | *
5 | * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey
6 | * http://juerglehni.com/ & https://puckey.studio/
7 | *
8 | * Distributed under the MIT license. See LICENSE file for details.
9 | *
10 | * All rights reserved.
11 | */
12 |
13 | // Node.js emulation layer of browser environment, based on jsdom with node-
14 | // canvas integration.
15 |
16 | var path = require('path');
17 | // Determine the name by which name the module was required (either 'paper',
18 | // 'paper-jsdom' or 'paper-jsdom-canvas'), and use this to determine if error
19 | // exceptions should be thrown or if loading should fail silently.
20 | var parent = module.parent && module.parent.parent,
21 | requireName = parent && path.basename(path.dirname(parent.filename));
22 | requireName = /^paper/.test(requireName) ? requireName : 'paper';
23 |
24 | var jsdom,
25 | self;
26 |
27 | try {
28 | jsdom = require('jsdom');
29 | } catch(e) {
30 | // Check the required module's name to see if it contains jsdom, and only
31 | // complain about its lack if the module requires it.
32 | if (/\bjsdom\b/.test(requireName)) {
33 | throw new Error('Unable to load jsdom module.');
34 | }
35 | }
36 |
37 | if (jsdom) {
38 | // Create our document and window objects through jsdom.
39 | /* global document:true, window:true */
40 | var document = new jsdom.JSDOM('
', {
41 | // Use the current working directory as the document's origin, so
42 | // requests to local files work correctly with CORS.
43 | url: 'file://' + process.cwd() + '/',
44 | resources: 'usable'
45 | });
46 | self = document.window;
47 | require('./canvas.js')(self, requireName);
48 | require('./xml.js')(self);
49 | } else {
50 | self = {
51 | navigator: {
52 | userAgent: 'Node.js (' + process.platform + '; U; rv:' +
53 | process.version + ')'
54 | }
55 | };
56 | }
57 |
58 | module.exports = self;
59 |
--------------------------------------------------------------------------------
/js/lib/paperjs-v0.12.17/node/canvas.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
3 | * http://paperjs.org/
4 | *
5 | * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey
6 | * http://juerglehni.com/ & https://puckey.studio/
7 | *
8 | * Distributed under the MIT license. See LICENSE file for details.
9 | *
10 | * All rights reserved.
11 | */
12 |
13 | // Add some useful extensions to HTMLCanvasElement:
14 | // - HTMLCanvasElement#type, so we can switch to a PDF canvas
15 | // - Various Node-Canvas methods, routed through from HTMLCanvasElement:
16 | // toBuffer, pngStream, createPNGStream, jpegStream, createJPEGStream
17 |
18 | module.exports = function(self, requireName) {
19 | var Canvas;
20 | try {
21 | Canvas = require('canvas').Canvas;
22 | } catch(error) {
23 | // Remove `self.window`, so we still have the global `self` reference,
24 | // but no `window` object:
25 | // - On the browser, this corresponds to a worker context.
26 | // - On Node.js, it basically means the canvas is missing or not working
27 | // which can be treated the same way.
28 | delete self.window;
29 | // Check the required module's name to see if it contains canvas, and
30 | // only complain about its lack if the module requires it.
31 | if (/\bcanvas\b/.test(requireName)) {
32 | throw new Error('Unable to load canvas module.');
33 | }
34 | return;
35 | }
36 |
37 | var HTMLCanvasElement = self.HTMLCanvasElement,
38 | idlUtils = require('jsdom/lib/jsdom/living/generated/utils');
39 |
40 | // Add fake HTMLCanvasElement#type property:
41 | Object.defineProperty(HTMLCanvasElement.prototype, 'type', {
42 | get: function() {
43 | var canvas = idlUtils.implForWrapper(this)._canvas;
44 | return canvas && canvas.type || 'image';
45 | },
46 |
47 | set: function(type) {
48 | // Allow replacement of internal node-canvas, so we can switch to a
49 | // PDF canvas.
50 | var impl = idlUtils.implForWrapper(this),
51 | size = impl._canvas || impl;
52 | impl._canvas = new Canvas(size.width, size.height, type);
53 | impl._context = null;
54 | }
55 | });
56 |
57 | // Extend HTMLCanvasElement with useful methods from the underlying Canvas:
58 | var methods = ['toBuffer', 'pngStream', 'createPNGStream', 'jpegStream',
59 | 'createJPEGStream'];
60 | methods.forEach(function(key) {
61 | HTMLCanvasElement.prototype[key] = function() {
62 | var canvas = idlUtils.implForWrapper(this)._canvas;
63 | return canvas[key].apply(canvas, arguments);
64 | };
65 | });
66 | };
67 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
19 | [ Help ] show/hide
20 |
21 |
22 |
23 | v : paste SVG
24 | s : export SVG
25 | p : pause / play
26 | w : toggle wireframe
27 | g : toggle gravity
28 | a : toggle airResistance
29 |
30 |
31 | ### 使い方 (この文面は上部の show/hide をクリックすると隠せます)
32 | * "v" を押すと、クリップボードのSVGコードを読み込みます。(Chrome 66以降)
33 |
34 | * ドラッグ&ドロップしたSVGファイルを読み込むこともできます。
35 |
36 | * 読み込み後、塗り色が黒 のものは位置が固定されます。
37 | それ以外のものは物理法則に従って動きます。
38 | ドラッグで動かすこともできます。
39 |
40 | * 読み込めるのはクローズパスだけです。
41 |
42 | * "p" を押すと、ポーズ/再生 を切り替えます。
43 |
44 | * "s" を押すと、SVGファイルを保存します。
45 |
46 | * "w" を押すと、ワイヤーフレーム表示を切り替えます。
47 |
48 | * "g" を押すと、無重力状態を切り替えます。
49 |
50 | * "a" を押すと、空気抵抗(通常/重い)を切り替えます。
51 |
52 | * w,g,a は、読み込み前に切り替えておくことができます。
53 |
54 | * その他の詳細はリポジトリ のREADME.mdをご覧ください。
55 |
56 |
57 |
58 | ### how to use (you can hide this text by clicking "show/hide" above)
59 | * Hit "v" to load SVG code in clipboard. (Chrome 66 or later)
60 | alternatively, drag and drop SVG file to load.
61 |
62 | * After loading, the objects filled with black stay on fixed position.
63 | Other objeces move according to the law of physics, and draggable.
64 |
65 | * It loads only closed paths.
66 |
67 | * Hit "p" to toggle pause/play.
68 | * Hit "s" to export SVG file.
69 | * Hit "w" to toggle wireframes.
70 | * Hit "g" to toggle gravity.
71 | * Hit "a" to toggle air resistance normal/HIGH
72 |
73 | * "w", "g" and "a" can be set before loading.
74 |
75 | * See README.md for further information.
76 |
77 |
78 | ----
79 | created by @shspage
80 | repository of this project is on GitHub
81 |
82 |
83 |
84 | 重力=0 ,
85 | 空気抵抗=通常 ,
86 | 表示=通常
87 |
88 |
89 | gravity=0 ,
90 | airResistance=normal ,
91 | display=normal
92 |
93 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/README_ja.md:
--------------------------------------------------------------------------------
1 | # simple-svg-physics-runner
2 |
3 | ### 概要
4 | * ファイルまたはクリップボードを介してSVGデータを読み込みます。
5 | * 読み込んだ形状は物理法則に従って動きます。
6 | * 表示内容はSVGデータとして出力できます。
7 |
8 | 
9 |
10 | * バージョン 1.1.2b 以降、重力の初期値は 0 になりました。通常の重力にする場合は "g" を押してください。
11 |
12 | ### 使い方
13 | * "v" を押すと、クリップボードのSVGコードを読み込みます。(Chrome 66以降)
14 |
15 | * ドラッグ&ドロップしたSVGファイルを読み込むこともできます。
16 |
17 | * 読み込めるのはクローズパスのみです。読み込み後、塗り色が**黒**のものは位置が固定されます。それ以外のものは物理法則に従って動きます。ドラッグで動かすこともできます。
18 |
19 | * "p" を押すと、ポーズ/再生 を切り替えます。
20 |
21 | * "s" を押すと、SVGファイルを出力します。
22 |
23 | * "w" を押すと、ワイヤーフレーム表示を切り替えます。
24 |
25 | * "g" を押すと、無重力状態を切り替えます。
26 |
27 | * "a" を押すと、空気抵抗(通常/重い)を切り替えます。
28 |
29 | * w,g,a は、読み込み前に切り替えておくことができます。
30 |
31 | ----
32 | ### 進んだ使い方(Illustrator)
33 |
34 | #### レイヤー名による制御
35 | * レイヤー名に「wrap」を含む場合、画面外に出たオブジェクトは画面の反対側から入ってきます。
36 | (重力を0に設定してからの使用をおすすめします。)
37 |
38 | #### グループ名による制御
39 | * グループ名が「chain」で始まる場合、グループ内のオブジェクトは垂直方向に連結され、上部で固定されます。
40 | video - https://twitter.com/shspage/status/1757720353638674790
41 | * 同様に「bridge」で始まる場合は左右方向に連結され、両橋が固定されます。
42 | * 同様に「loop」で始まる場合はループ状に連結されます。(それぞれの最も近い・遠いオブジェクトと連結します。)
43 | * 「chain」で始まるグループ名が「 as is」(asの前に空白が必要)を含む場合、元のオブジェクトの位置を維持して連結します。
44 |
45 | #### オブジェクト名による制御
46 | * 名前が「hangN」(Nは自然数) を含む場合、上から吊り下げる線を生成します。Nが線の長さになります。加えて「offsetN」(N:-0.5〜0.5)
47 | を含む場合、線につなぐ位置を縦幅に対する比率でずらします。加えて「#N」(Nは3桁または6桁の16進数)を含む場合、線の色になります。「hidden」を含む場合、線は描画されません。これらはグループやレイヤーの名前に入っていても機能します。
48 |
49 | ### 使ってみる
50 | https://shspage.github.io/simple-svg-physics-runner/
51 |
52 | ### 補足:
53 | * クリップボードからの読み込みはhttpプロトコルでは動作しません(httpsかfileで動作)
54 | * SVGデータ中の凸でない多角形は自動的に凸多角形の集合体になります。SVG保存時には元の形状が出力されます。
55 | 凸多角形への分割は、複雑な図形ではうまくいかない場合があります。
56 | * 【重要】Illustratorでは、SVGコード出力・生成時に、元のパスが自動的に変更されることがあります。
57 | たとえば **「線の位置」が「中央」でない線** は、線色を塗り色としてアウトライン化された複合パスになります。
58 | * 積み重なった物体などをSVG出力すると、物体の端がお互いに食い込んでいる箇所に気が付くかもしれません。
59 | コンピュータの処理能力に余裕があれば、simple-svg-physics-runner.js 冒頭の ENGINE_POSITION_ITERATIONS を増やすことで、これを緩和できると思われます。
60 | (精度向上のためにこの値を調整することが適切なのかは、私もまだよくわかりませんが。
61 | いずれにしても、物理エンジンにとってある程度の誤差は処理の安定性のために必要なものなのかもしれません。)
62 | 上記のスクリプト冒頭部には、他にも変更可能な定数がいくつかあります。しかし全ての属性を網羅しているわけではありません。
63 | 詳細は Matter.js のドキュメントを参照してください。
64 |
65 | * IllustratorのCMYKモードでの黒(K100)は、生成されるSVGコードでは#000になりません。つまり位置を固定する対象になりません。
66 | このため、RGBがぞれぞれ (35,24,21) 以下の場合は黒と見なして#000に変換し、エクスポート時も#000にしています。
67 | この色変換を行いたくない場合、simple-svg-physics-runner.js で AS_BLACK_THRESHOLD_RGB を[0,0,0] に設定してください。
68 |
69 | * 複合パスは「穴」がない図形として描画されます。穴に見えるのは背景色で塗られた図形です。このため穴の内部に他の図形を置くことはできません。
70 |
71 |
72 | ### 変更履歴
73 | #### v.1.2.0b2
74 | * "hang" で生成された線をエクスポートされるよう修正。
75 |
76 | #### v.1.2.0b1
77 | * オブジェクト名中の "hang" 等による制御を追加。
78 | * SVG要素の属性に data-name がある場合、idに代えて使用するよう変更。
79 |
80 | #### v.1.2.0b
81 | * 曲線を適宜折れ線に変換する処理を追加。
82 | * 物理エンジンで扱うにあたり多角形の分割や折れ線化をした場合でも、エクスポート時は元のSVGの形状を保存するように変更。
83 |
84 | #### v.1.1.2b1
85 | * 重力が 0 の場合の文字表示を赤色に変更。通常の重力の値を 1 に変更。
86 | * bodyにrestitution(反発)の値を設定。
87 | * "chain", "bridge" の最後の constraint が固い問題に暫定的に対応。
88 |
89 | #### v.1.1.2b
90 | * 重力の初期値を 0 に変更。通常の重力の値を 0.8 に変更。
91 | * "wrap"機能を追加。(matter-wrapのバージョン整合性が一致しませんが動作しているようです。)
92 | * 複合パスの描画に対応。(note の注意事項もご覧ください。)
93 |
94 | #### v.1.1.1
95 | * Illustrator: グループ名が"loop"で始まる場合に、中のオブジェクトをループ状に連結する機能を追加。
96 | * Illustrator: グループ名が"chain"で始まり、" as is"を含む場合に、中のオブジェクトを元の位置を維持して連結する機能を追加。
97 |
98 | #### v.1.1.0
99 | * 必須ライブラリを更新しました。
100 | * canvasからスクロールバーを取り除きました。
101 | * Illustrator: グループ名が"bridge"で始まる場合に、中のオブジェクトを左右方向に連結する機能を追加。
102 |
103 | #### v.1.0.2
104 | * RGB値が全て設定値以下の場合黒と見なす処理を追加。(AS_BLACK_THRESHOLD_RGB)
105 | * Illustrator: グループ名が"chain"で始まる場合に、中のオブジェクトを上下方向に連結する機能を追加。
106 |
107 | #### v.1.0.1
108 | * 幅と高さの差、および幅を直径として算出した周長・面積と実際の値との差が設定値未満であれば、パスを円と見なすようにしました。
109 | (Inkscapeで作成したSVGへの対応用。判別方法はまだ検討が必要かもしれません。)
110 | * 読み込んだ図形の左上をキャンバスの左上に合わせるようにしました。
111 |
112 | ### TODO
113 | * FireFoxでのクリップボードからの読み込み
114 | * 曲線を自動的に折れ線化する
115 |
116 | ### License
117 | * Copyright(c) 2018 Hiroyuki Sato
118 | https://github.com/shspage/simple-svg-physics-runner
119 | This software is distributed under the MIT License.
120 | See the LICENSE.txt for details.
121 |
122 | This software uses the following libraries that may have licenses
123 | differing from that of the software itself. You can find the
124 | libraries and their respective licenses below.
125 |
126 | #### required libraries (including in this repo)
127 | * jQuery (v3.7.1)
128 | License MIT
129 | (c) OpenJS Foundation and other contributors | jquery.org/license
130 |
131 | * matter-js (0.19.0) http://brm.io/matter-js/
132 | License MIT
133 | Copyright (c) Liam Brummitt and contributors.
134 |
135 | * Paper.js (v0.12.17) http://paperjs.org/
136 | License MIT
137 | Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey
138 | http://juerglehni.com/ & https://puckey.studio/
139 |
140 | * decomp.js (https://github.com/schteppe/poly-decomp.js)
141 | License MIT
142 | Copyright (c) 2013 Stefan Hedman
143 |
--------------------------------------------------------------------------------
/js/lib/paperjs-v0.12.17/node/extend.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
3 | * http://paperjs.org/
4 | *
5 | * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey
6 | * http://juerglehni.com/ & https://puckey.studio/
7 | *
8 | * Distributed under the MIT license. See LICENSE file for details.
9 | *
10 | * All rights reserved.
11 | */
12 |
13 | var fs = require('fs'),
14 | path = require('path');
15 |
16 | module.exports = function(paper) {
17 | if (paper.PaperScript) {
18 | var sourceMapSupport = 'require("source-map-support").install(paper.PaperScript.sourceMapSupport);\n',
19 | sourceMaps = {};
20 |
21 | paper.PaperScript.sourceMapSupport = {
22 | retrieveSourceMap: function(source) {
23 | var map = sourceMaps[source];
24 | return map ? { url: source, map: map } : null;
25 | }
26 | };
27 |
28 | // Register the .pjs extension for automatic compilation as PaperScript
29 | require.extensions['.pjs'] = function(module, filename) {
30 | // Requiring a PaperScript on Node.js returns an initialize method which
31 | // needs to receive a Canvas object when called and returns the
32 | // PaperScope.
33 | module.exports = function(canvas) {
34 | var source = fs.readFileSync(filename, 'utf8'),
35 | code = sourceMapSupport + source,
36 | compiled = paper.PaperScript.compile(code, {
37 | url: filename,
38 | source: source,
39 | sourceMaps: true,
40 | offset: -1 // remove sourceMapSupport...
41 | }),
42 | scope = new paper.PaperScope();
43 | // Keep track of sourceMaps so retrieveSourceMap() can link them up
44 | scope.setup(canvas);
45 | scope.__filename = filename;
46 | scope.__dirname = path.dirname(filename);
47 | // Expose core methods and values
48 | scope.require = require;
49 | scope.console = console;
50 | sourceMaps[filename] = compiled.map;
51 | paper.PaperScript.execute(compiled, scope);
52 | return scope;
53 | };
54 | };
55 | }
56 |
57 | paper.PaperScope.inject({
58 | createCanvas: function(width, height, type) {
59 | // Do not use CanvasProvider.getCanvas(), since we may be changing
60 | // the underlying node-canvas when requesting PDF support, and don't
61 | // want to release it after back into the pool.
62 | var canvas = paper.document.createElement('canvas');
63 | canvas.width = width;
64 | canvas.height = height;
65 | canvas.type = type;
66 | return canvas;
67 | },
68 |
69 | /**
70 | * @deprecated, use use {@link #createCanvas(width, height)} instead.
71 | */
72 | Canvas: '#createCanvas'
73 | });
74 |
75 | // Override requestAnimationFrame() to avoid setInterval() timers.
76 | // NOTE: In Node.js, we only support manual updating for now, but
77 | // View#exportFrames() below offers a way to emulate animations by exporting
78 | // them frame by frame at the given frame-rate.
79 | paper.DomEvent.requestAnimationFrame = function(callback) {
80 | };
81 |
82 | // Node.js based image exporting code.
83 | paper.CanvasView.inject({
84 | // DOCS: CanvasView#exportFrames(options);
85 | exportFrames: function(options) {
86 | options = paper.Base.set({
87 | fps: 30,
88 | prefix: 'frame-',
89 | amount: 1,
90 | format: 'png' // Supported: 'png' or 'jpeg'
91 | }, options);
92 | if (!options.directory)
93 | throw new Error('Missing options.directory');
94 | if (options.format && !/^(jpeg|png)$/.test(options.format))
95 | throw new Error('Unsupported format. Use "png" or "jpeg"');
96 | var view = this,
97 | count = 0,
98 | frameDuration = 1 / options.fps,
99 | startTime = Date.now(),
100 | lastTime = startTime,
101 | padding = options.padding || ((options.amount - 1) + '').length,
102 | paddedStr = Array(padding + 1).join('0');
103 |
104 | // Start exporting frames by exporting the first frame:
105 | exportFrame(options);
106 |
107 | function exportFrame() {
108 | // Convert to a Base object, for #toString()
109 | view.emit('frame', new paper.Base({
110 | delta: frameDuration,
111 | time: frameDuration * count,
112 | count: count
113 | }));
114 | var file = path.join(options.directory,
115 | options.prefix + (paddedStr + count).slice(-padding)
116 | + '.' + options.format);
117 | var out = view.exportImage(file, function() {
118 | // Once the file has been closed, export the next fame:
119 | var then = Date.now();
120 | if (options.onProgress) {
121 | options.onProgress({
122 | count: count,
123 | amount: options.amount,
124 | percentage: Math.round((count + 1) / options.amount
125 | * 10000) / 100,
126 | time: then - startTime,
127 | delta: then - lastTime
128 | });
129 | }
130 | lastTime = then;
131 | if (++count < options.amount) {
132 | exportFrame();
133 | } else {
134 | // Call onComplete handler when finished:
135 | if (options.onComplete) {
136 | options.onComplete();
137 | }
138 | }
139 | });
140 | }
141 | },
142 |
143 | // DOCS: CanvasView#exportImage(path, callback);
144 | exportImage: function(path, callback) {
145 | this.update();
146 | var out = fs.createWriteStream(path),
147 | format = /\.jp(e?)g$/.test(path) ? 'jpeg' : 'png',
148 | stream = this._element[format + 'Stream']();
149 | stream.pipe(out);
150 | if (callback) {
151 | out.on('close', callback);
152 | }
153 | return out;
154 | }
155 | });
156 | };
157 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # simple-svg-physics-runner
2 |
3 | [Readme in Japanese](https://github.com/shspage/simple-svg-physics-runner/blob/master/README_ja.md)
4 |
5 | ### Summary
6 | * Loading SVG data on the browser via file or clipboard.
7 | * Applying physical effects to it.
8 | * Putting out SVG data.
9 |
10 | 
11 |
12 | * The initial value of **gravity** is 0 in version 1.1.2b or later. Press "g" for normal gravity.
13 |
14 | ### How to use
15 | * Hit "v" to load SVG code from clipboard. (Chrome 66 or later)
16 | alternatively, drag and drop SVG file to load.
17 |
18 | * It loads only closed paths. After loading, the objects filled with **black** stay on fixed position.
19 | Other objects move according to the law of physics, and draggable.
20 |
21 | * Hit "p" to toggle pause/play.
22 | * Hit "s" to export SVG file.
23 | * Hit "w" to toggle wireframes.
24 | * Hit "g" to toggle gravity 0/1.
25 | * Hit "a" to toggle air resistance normal/HIGH
26 |
27 | * "w", "g" and "a" can be set before loading.
28 |
29 | ----
30 | ### Advanced usage (Illustrator)
31 |
32 | #### Control by LAYER name
33 | * If the layer name includes "wrap", objects that go out of the screen will come in from the opposite side of the screen.
34 | (I recommend setting gravity to 0 before use.)
35 |
36 | #### Control by GROUP name
37 | * If the group name starts with "chain", the objects in the group are chained vertically, anchored at the top.
38 | video - https://twitter.com/shspage/status/1757720353638674790
39 | * Similarly, if it starts with "bridge", the ends are fixed and connected horizontally.
40 | * Similarly, if it starts with "loop", it will be connected in a loop. (Connect with each nearest and farthest object.)
41 | * If the group name starts with "chain" and includes " as is"(it needs first space), the original objects will be connected while maintaining their positions.
42 |
43 | #### Control by OBJECT name
44 | * If the name contains "hangN" (N is a natural number), it will generate a line hanging from above. N is the length of the line.
45 | In addition, if "offsetN" (N: -0.5 to 0.5) is included, the position connected to the line will be shifted by the ratio to the vertical width. In addition, if "#N" (N is a 3-digit or 6-digit hexadecimal number) is included, the line color will be changed. If it contains "hidden", no line will be drawn. These words work even if they are in the name of a group or layer.
46 |
47 | ### Try using
48 | https://shspage.github.io/simple-svg-physics-runner/
49 |
50 | ### Note:
51 | * Loading from the clipboard does not work with the http protocol (works with https or file)
52 | * Non-convex polygons in SVG data are automatically converted to a group of convex polygons,
53 | and when you save them as SVG, they are exported as the original SVG shapes.
54 | Splitting into convex polygons may not work well for complex shapes.
55 | * **IMPORTANT** : In Illustrator, the original path may be changed automatically when SVG code is generated.
56 | For example, **a line whose "line position" is not "center"** is converted to a compound path with fill color of original line color.
57 | * When stacking objects are output as SVG, you may notice where the edges of the objects dig into each other.
58 | If there is enough processing power of your computer, you can mitigate this by increasing ENGINE_POSITION_ITERATIONS at the beginning of simple-svg-physics-runner.js.
59 | (Though I'm not sure whether it is appropriate to adjust this value to improve accuracy.
60 | Either way, I guess, for a physics engine, some error may be necessary for stability of processing.)
61 | There are some other constants you can change in the above script beginning part.
62 | However, it does not cover all attributes.
63 | For the details about attributes, please refer to Matter.js document.
64 |
65 | * In Illustrator, black (K100) in CMYK mode is not converted to #000 in the generated SVG code.
66 | This means the object doesn't stay on fixed position.
67 | So, if RGB is (35,24,21) or lower respectively, it is considered black and converted to #000,
68 | and it is also set to #000 when exporting.
69 | If you do not want to perform this color conversion, please set AS_BLACK_THRESHOLD_RGB to [0,0,0]
70 | in simple-svg-physics-runner.js.
71 |
72 | * Compound paths are drawn as shapes with no "holes". What looks like a hole is a shape painted with the background color. Therefore, you cannot place other shapes inside the "hole".
73 |
74 |
75 | ### ChangeLog
76 | #### v.1.2.0b2
77 | * Fixed lines generated by "hang" to be exported.
78 |
79 | #### v.1.2.0b1
80 | * Added control using "hang" etc. in object name.
81 | * If "data-name" is in attributes of an SVG element, it will be used instead of "id".
82 |
83 | #### v.1.2.0b
84 | * Added processing to convert curves to polylines as appropriate.
85 | * Exports the original SVG shapes, even if polygons and curves are reshaped to handle with the physics engine.
86 |
87 | #### v.1.1.2b1
88 | * Changed the text display to red when gravity is 0. Changed normal gravity value to 1.
89 | * Set the restitution value to all bodies.
90 | * Tentative fix for the problem where the last constraint of "chain" and "bridge" is hard.
91 |
92 | #### v.1.1.2b
93 | * Set default gravity to 0, normal gravity to 0.8.
94 | * Added "wrap" feature. (matter-wrap plugin appears to be working despite the required version unmatch)
95 | * Supports drawing compound paths. (See also note section for additional notes.)
96 |
97 | #### v.1.1.1
98 | * Illustrator: Added a function to connect objects in a loop when the group name starts with "loop".
99 | * Illustrator: If the group name starts with "chain" and includes " as is"(it needs first space), the original objects will be connected while maintaining their positions.
100 |
101 | #### v.1.1.0
102 | * updated required libraries.
103 | * removed canvas scrollbars.
104 | * Illustrator: Added a function to connect objects in the horizontal direction when the group name starts with "bridge".
105 |
106 | #### v.1.0.2
107 | * Added a setting of upper limit of RGB values to be considered as black. (AS_BLACK_THRESHOLD_RGB)
108 | * Illustrator: Added a function to connect objects in the vertical direction when the group name starts with "chain".
109 |
110 | #### v.1.0.1
111 | * A path is regarded as a circle if the difference between the width and the height and the difference between the circumference / area calculated from the width as the diameter and the actual value is less than the set value.
112 | (This is for SVG files created with Inkscape. Classification methods may need further consideration.)
113 | * Adjusting the upper left corner of the loaded figure to be at the upper left of the canvas.
114 |
115 | ### TODO
116 | * Loading from the clipboard with firefox
117 | * flattening curves including ellipses automatically
118 |
119 | ### License
120 | * Copyright(c) 2018 Hiroyuki Sato
121 | https://github.com/shspage/simple-svg-physics-runner
122 | This software is distributed under the MIT License.
123 | See the LICENSE.txt for details.
124 |
125 | This software uses the following libraries that may have licenses
126 | differing from that of the software itself. You can find the
127 | libraries and their respective licenses below.
128 |
129 | #### required libraries (including in this repo)
130 | * jQuery (v3.7.1)
131 | License MIT
132 | (c) OpenJS Foundation and other contributors | jquery.org/license
133 |
134 | * matter-js (0.19.0) http://brm.io/matter-js/
135 | License MIT
136 | Copyright (c) Liam Brummitt and contributors.
137 |
138 | * Paper.js (v0.12.17) http://paperjs.org/
139 | License MIT
140 | Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey
141 | http://juerglehni.com/ & https://puckey.studio/
142 |
143 | * decomp.js (https://github.com/schteppe/poly-decomp.js)
144 | License MIT
145 | Copyright (c) 2013 Stefan Hedman
146 |
--------------------------------------------------------------------------------
/js/lib/matter-attractors.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * matter-attractors 0.1.6 by Liam Brummitt 2017-05-15
3 | * https://github.com/liabru/matter-attractors
4 | * License MIT
5 | */
6 | (function webpackUniversalModuleDefinition(root, factory) {
7 | if(typeof exports === 'object' && typeof module === 'object')
8 | module.exports = factory(require("matter-js"));
9 | else if(typeof define === 'function' && define.amd)
10 | define(["matter-js"], factory);
11 | else if(typeof exports === 'object')
12 | exports["MatterAttractors"] = factory(require("matter-js"));
13 | else
14 | root["MatterAttractors"] = factory(root["Matter"]);
15 | })(this, function(__WEBPACK_EXTERNAL_MODULE_0__) {
16 | return /******/ (function(modules) { // webpackBootstrap
17 | /******/ // The module cache
18 | /******/ var installedModules = {};
19 |
20 | /******/ // The require function
21 | /******/ function __webpack_require__(moduleId) {
22 |
23 | /******/ // Check if module is in cache
24 | /******/ if(installedModules[moduleId])
25 | /******/ return installedModules[moduleId].exports;
26 |
27 | /******/ // Create a new module (and put it into the cache)
28 | /******/ var module = installedModules[moduleId] = {
29 | /******/ i: moduleId,
30 | /******/ l: false,
31 | /******/ exports: {}
32 | /******/ };
33 |
34 | /******/ // Execute the module function
35 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
36 |
37 | /******/ // Flag the module as loaded
38 | /******/ module.l = true;
39 |
40 | /******/ // Return the exports of the module
41 | /******/ return module.exports;
42 | /******/ }
43 |
44 |
45 | /******/ // expose the modules object (__webpack_modules__)
46 | /******/ __webpack_require__.m = modules;
47 |
48 | /******/ // expose the module cache
49 | /******/ __webpack_require__.c = installedModules;
50 |
51 | /******/ // identity function for calling harmony imports with the correct context
52 | /******/ __webpack_require__.i = function(value) { return value; };
53 |
54 | /******/ // define getter function for harmony exports
55 | /******/ __webpack_require__.d = function(exports, name, getter) {
56 | /******/ if(!__webpack_require__.o(exports, name)) {
57 | /******/ Object.defineProperty(exports, name, {
58 | /******/ configurable: false,
59 | /******/ enumerable: true,
60 | /******/ get: getter
61 | /******/ });
62 | /******/ }
63 | /******/ };
64 |
65 | /******/ // getDefaultExport function for compatibility with non-harmony modules
66 | /******/ __webpack_require__.n = function(module) {
67 | /******/ var getter = module && module.__esModule ?
68 | /******/ function getDefault() { return module['default']; } :
69 | /******/ function getModuleExports() { return module; };
70 | /******/ __webpack_require__.d(getter, 'a', getter);
71 | /******/ return getter;
72 | /******/ };
73 |
74 | /******/ // Object.prototype.hasOwnProperty.call
75 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
76 |
77 | /******/ // __webpack_public_path__
78 | /******/ __webpack_require__.p = "/libs";
79 |
80 | /******/ // Load entry module and return exports
81 | /******/ return __webpack_require__(__webpack_require__.s = 1);
82 | /******/ })
83 | /************************************************************************/
84 | /******/ ([
85 | /* 0 */
86 | /***/ (function(module, exports) {
87 |
88 | module.exports = __WEBPACK_EXTERNAL_MODULE_0__;
89 |
90 | /***/ }),
91 | /* 1 */
92 | /***/ (function(module, exports, __webpack_require__) {
93 |
94 | "use strict";
95 |
96 |
97 | var Matter = __webpack_require__(0);
98 |
99 | /**
100 | * An attractors plugin for matter.js.
101 | * See the readme for usage and examples.
102 | * @module MatterAttractors
103 | */
104 | var MatterAttractors = {
105 | // plugin meta
106 | name: 'matter-attractors', // PLUGIN_NAME
107 | version: '0.1.4', // PLUGIN_VERSION
108 | for: 'matter-js@^0.12.0',
109 |
110 | // installs the plugin where `base` is `Matter`
111 | // you should not need to call this directly.
112 | install: function install(base) {
113 | base.after('Body.create', function () {
114 | MatterAttractors.Body.init(this);
115 | });
116 |
117 | base.before('Engine.update', function (engine) {
118 | MatterAttractors.Engine.update(engine);
119 | });
120 | },
121 |
122 | Body: {
123 | /**
124 | * Initialises the `body` to support attractors.
125 | * This is called automatically by the plugin.
126 | * @function MatterAttractors.Body.init
127 | * @param {Matter.Body} body The body to init.
128 | * @returns {void} No return value.
129 | */
130 | init: function init(body) {
131 | body.plugin.attractors = body.plugin.attractors || [];
132 | }
133 | },
134 |
135 | Engine: {
136 | /**
137 | * Applies all attractors for all bodies in the `engine`.
138 | * This is called automatically by the plugin.
139 | * @function MatterAttractors.Engine.update
140 | * @param {Matter.Engine} engine The engine to update.
141 | * @returns {void} No return value.
142 | */
143 | update: function update(engine) {
144 | var world = engine.world,
145 | bodies = Matter.Composite.allBodies(world);
146 |
147 | for (var i = 0; i < bodies.length; i += 1) {
148 | var bodyA = bodies[i],
149 | attractors = bodyA.plugin.attractors;
150 |
151 | if (attractors && attractors.length > 0) {
152 | for (var j = i + 1; j < bodies.length; j += 1) {
153 | var bodyB = bodies[j];
154 |
155 | for (var k = 0; k < attractors.length; k += 1) {
156 | var attractor = attractors[k],
157 | forceVector = attractor;
158 |
159 | if (Matter.Common.isFunction(attractor)) {
160 | forceVector = attractor(bodyA, bodyB);
161 | }
162 |
163 | if (forceVector) {
164 | Matter.Body.applyForce(bodyB, bodyB.position, forceVector);
165 | }
166 | }
167 | }
168 | }
169 | }
170 | }
171 | },
172 |
173 | /**
174 | * Defines some useful common attractor functions that can be used
175 | * by pushing them to your body's `body.plugin.attractors` array.
176 | * @namespace MatterAttractors.Attractors
177 | * @property {number} gravityConstant The gravitational constant used by the gravity attractor.
178 | */
179 | Attractors: {
180 | gravityConstant: 0.001,
181 |
182 | /**
183 | * An attractor function that applies Newton's law of gravitation.
184 | * Use this by pushing `MatterAttractors.Attractors.gravity` to your body's `body.plugin.attractors` array.
185 | * The gravitational constant defaults to `0.001` which you can change
186 | * at `MatterAttractors.Attractors.gravityConstant`.
187 | * @function MatterAttractors.Attractors.gravity
188 | * @param {Matter.Body} bodyA The first body.
189 | * @param {Matter.Body} bodyB The second body.
190 | * @returns {void} No return value.
191 | */
192 | gravity: function gravity(bodyA, bodyB) {
193 | // use Newton's law of gravitation
194 | var bToA = Matter.Vector.sub(bodyB.position, bodyA.position),
195 | distanceSq = Matter.Vector.magnitudeSquared(bToA) || 0.0001,
196 | normal = Matter.Vector.normalise(bToA),
197 | magnitude = -MatterAttractors.Attractors.gravityConstant * (bodyA.mass * bodyB.mass / distanceSq),
198 | force = Matter.Vector.mult(normal, magnitude);
199 |
200 | // to apply forces to both bodies
201 | Matter.Body.applyForce(bodyA, bodyA.position, Matter.Vector.neg(force));
202 | Matter.Body.applyForce(bodyB, bodyB.position, force);
203 | }
204 | }
205 | };
206 |
207 | Matter.Plugin.register(MatterAttractors);
208 |
209 | module.exports = MatterAttractors;
210 |
211 | /**
212 | * @namespace Matter.Body
213 | * @see http://brm.io/matter-js/docs/classes/Body.html
214 | */
215 |
216 | /**
217 | * This plugin adds a new property `body.plugin.attractors` to instances of `Matter.Body`.
218 | * This is an array of callback functions that will be called automatically
219 | * for every pair of bodies, on every engine update.
220 | * @property {Function[]} body.plugin.attractors
221 | * @memberof Matter.Body
222 | */
223 |
224 | /**
225 | * An attractor function calculates the force to be applied
226 | * to `bodyB`, it should either:
227 | * - return the force vector to be applied to `bodyB`
228 | * - or apply the force to the body(s) itself
229 | * @callback AttractorFunction
230 | * @param {Matter.Body} bodyA
231 | * @param {Matter.Body} bodyB
232 | * @returns {Vector|undefined} a force vector (optional)
233 | */
234 |
235 | /***/ })
236 | /******/ ]);
237 | });
--------------------------------------------------------------------------------
/js/lib/matter-wrap.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * matter-wrap 0.2.0 by Liam Brummitt 2017-07-04
3 | * https://github.com/liabru/matter-wrap
4 | * License MIT
5 | */
6 | (function webpackUniversalModuleDefinition(root, factory) {
7 | if(typeof exports === 'object' && typeof module === 'object')
8 | module.exports = factory(require("matter-js"));
9 | else if(typeof define === 'function' && define.amd)
10 | define(["matter-js"], factory);
11 | else if(typeof exports === 'object')
12 | exports["MatterWrap"] = factory(require("matter-js"));
13 | else
14 | root["MatterWrap"] = factory(root["Matter"]);
15 | })(this, function(__WEBPACK_EXTERNAL_MODULE_0__) {
16 | return /******/ (function(modules) { // webpackBootstrap
17 | /******/ // The module cache
18 | /******/ var installedModules = {};
19 |
20 | /******/ // The require function
21 | /******/ function __webpack_require__(moduleId) {
22 |
23 | /******/ // Check if module is in cache
24 | /******/ if(installedModules[moduleId])
25 | /******/ return installedModules[moduleId].exports;
26 |
27 | /******/ // Create a new module (and put it into the cache)
28 | /******/ var module = installedModules[moduleId] = {
29 | /******/ i: moduleId,
30 | /******/ l: false,
31 | /******/ exports: {}
32 | /******/ };
33 |
34 | /******/ // Execute the module function
35 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
36 |
37 | /******/ // Flag the module as loaded
38 | /******/ module.l = true;
39 |
40 | /******/ // Return the exports of the module
41 | /******/ return module.exports;
42 | /******/ }
43 |
44 |
45 | /******/ // expose the modules object (__webpack_modules__)
46 | /******/ __webpack_require__.m = modules;
47 |
48 | /******/ // expose the module cache
49 | /******/ __webpack_require__.c = installedModules;
50 |
51 | /******/ // identity function for calling harmony imports with the correct context
52 | /******/ __webpack_require__.i = function(value) { return value; };
53 |
54 | /******/ // define getter function for harmony exports
55 | /******/ __webpack_require__.d = function(exports, name, getter) {
56 | /******/ if(!__webpack_require__.o(exports, name)) {
57 | /******/ Object.defineProperty(exports, name, {
58 | /******/ configurable: false,
59 | /******/ enumerable: true,
60 | /******/ get: getter
61 | /******/ });
62 | /******/ }
63 | /******/ };
64 |
65 | /******/ // getDefaultExport function for compatibility with non-harmony modules
66 | /******/ __webpack_require__.n = function(module) {
67 | /******/ var getter = module && module.__esModule ?
68 | /******/ function getDefault() { return module['default']; } :
69 | /******/ function getModuleExports() { return module; };
70 | /******/ __webpack_require__.d(getter, 'a', getter);
71 | /******/ return getter;
72 | /******/ };
73 |
74 | /******/ // Object.prototype.hasOwnProperty.call
75 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
76 |
77 | /******/ // __webpack_public_path__
78 | /******/ __webpack_require__.p = "/libs";
79 |
80 | /******/ // Load entry module and return exports
81 | /******/ return __webpack_require__(__webpack_require__.s = 1);
82 | /******/ })
83 | /************************************************************************/
84 | /******/ ([
85 | /* 0 */
86 | /***/ (function(module, exports) {
87 |
88 | module.exports = __WEBPACK_EXTERNAL_MODULE_0__;
89 |
90 | /***/ }),
91 | /* 1 */
92 | /***/ (function(module, exports, __webpack_require__) {
93 |
94 | "use strict";
95 |
96 |
97 | var Matter = __webpack_require__(0);
98 |
99 | /**
100 | * A coordinate wrapping plugin for matter.js.
101 | * See the readme for usage and examples.
102 | * @module MatterWrap
103 | */
104 | var MatterWrap = {
105 | // plugin meta
106 | name: 'matter-wrap', // PLUGIN_NAME
107 | version: '0.1.3', // PLUGIN_VERSION
108 | for: 'matter-js@^0.12.0',
109 |
110 | // installs the plugin where `base` is `Matter`
111 | // you should not need to call this directly.
112 | install: function install(base) {
113 | base.after('Engine.update', function () {
114 | MatterWrap.Engine.update(this);
115 | });
116 | },
117 |
118 | Engine: {
119 | /**
120 | * Updates the engine by wrapping bodies and composites inside `engine.world`.
121 | * This is called automatically by the plugin.
122 | * @function MatterWrap.Engine.update
123 | * @param {Matter.Engine} engine The engine to update.
124 | * @returns {void} No return value.
125 | */
126 | update: function update(engine) {
127 | var world = engine.world,
128 | bodies = Matter.Composite.allBodies(world),
129 | composites = Matter.Composite.allComposites(world);
130 |
131 | for (var i = 0; i < bodies.length; i += 1) {
132 | var body = bodies[i];
133 |
134 | if (body.plugin.wrap) {
135 | MatterWrap.Body.wrap(body, body.plugin.wrap);
136 | }
137 | }
138 |
139 | for (i = 0; i < composites.length; i += 1) {
140 | var composite = composites[i];
141 |
142 | if (composite.plugin.wrap) {
143 | MatterWrap.Composite.wrap(composite, composite.plugin.wrap);
144 | }
145 | }
146 | }
147 | },
148 |
149 | Bounds: {
150 | /**
151 | * Returns a translation vector that wraps the `objectBounds` inside the `bounds`.
152 | * @function MatterWrap.Bounds.wrap
153 | * @param {Matter.Bounds} objectBounds The bounds of the object to wrap inside the bounds.
154 | * @param {Matter.Bounds} bounds The bounds to wrap the body inside.
155 | * @returns {?Matter.Vector} A translation vector (only if wrapping is required).
156 | */
157 | wrap: function wrap(objectBounds, bounds) {
158 | var x = null,
159 | y = null;
160 |
161 | if (typeof bounds.min.x !== 'undefined' && typeof bounds.max.x !== 'undefined') {
162 | if (objectBounds.min.x > bounds.max.x) {
163 | x = bounds.min.x - objectBounds.max.x;
164 | } else if (objectBounds.max.x < bounds.min.x) {
165 | x = bounds.max.x - objectBounds.min.x;
166 | }
167 | }
168 |
169 | if (typeof bounds.min.y !== 'undefined' && typeof bounds.max.y !== 'undefined') {
170 | if (objectBounds.min.y > bounds.max.y) {
171 | y = bounds.min.y - objectBounds.max.y;
172 | } else if (objectBounds.max.y < bounds.min.y) {
173 | y = bounds.max.y - objectBounds.min.y;
174 | }
175 | }
176 |
177 | if (x !== null || y !== null) {
178 | return {
179 | x: x || 0,
180 | y: y || 0
181 | };
182 | }
183 | }
184 | },
185 |
186 | Body: {
187 | /**
188 | * Wraps the `body` position such that it always stays within the given bounds.
189 | * Upon crossing a boundary the body will appear on the opposite side of the bounds,
190 | * while maintaining its velocity.
191 | * This is called automatically by the plugin.
192 | * @function MatterWrap.Body.wrap
193 | * @param {Matter.Body} body The body to wrap.
194 | * @param {Matter.Bounds} bounds The bounds to wrap the body inside.
195 | * @returns {?Matter.Vector} The translation vector that was applied (only if wrapping was required).
196 | */
197 | wrap: function wrap(body, bounds) {
198 | var translation = MatterWrap.Bounds.wrap(body.bounds, bounds);
199 |
200 | if (translation) {
201 | Matter.Body.translate(body, translation);
202 | }
203 |
204 | return translation;
205 | }
206 | },
207 |
208 | Composite: {
209 | /**
210 | * Returns the union of the bounds of all of the composite's bodies
211 | * (not accounting for constraints).
212 | * @function MatterWrap.Composite.bounds
213 | * @param {Matter.Composite} composite The composite.
214 | * @returns {Matter.Bounds} The composite bounds.
215 | */
216 | bounds: function bounds(composite) {
217 | var bodies = Matter.Composite.allBodies(composite),
218 | vertices = [];
219 |
220 | for (var i = 0; i < bodies.length; i += 1) {
221 | var body = bodies[i];
222 | vertices.push(body.bounds.min, body.bounds.max);
223 | }
224 |
225 | return Matter.Bounds.create(vertices);
226 | },
227 |
228 | /**
229 | * Wraps the `composite` position such that it always stays within the given bounds.
230 | * Upon crossing a boundary the composite will appear on the opposite side of the bounds,
231 | * while maintaining its velocity.
232 | * This is called automatically by the plugin.
233 | * @function MatterWrap.Composite.wrap
234 | * @param {Matter.Composite} composite The composite to wrap.
235 | * @param {Matter.Bounds} bounds The bounds to wrap the composite inside.
236 | * @returns {?Matter.Vector} The translation vector that was applied (only if wrapping was required).
237 | */
238 | wrap: function wrap(composite, bounds) {
239 | var translation = MatterWrap.Bounds.wrap(MatterWrap.Composite.bounds(composite), bounds);
240 |
241 | if (translation) {
242 | Matter.Composite.translate(composite, translation);
243 | }
244 |
245 | return translation;
246 | }
247 | }
248 | };
249 |
250 | Matter.Plugin.register(MatterWrap);
251 |
252 | module.exports = MatterWrap;
253 |
254 | /**
255 | * @namespace Matter.Body
256 | * @see http://brm.io/matter-js/docs/classes/Body.html
257 | */
258 |
259 | /**
260 | * This plugin adds a new property `body.plugin.wrap` to instances of `Matter.Body`.
261 | * This is a `Matter.Bounds` instance that specifies the wrapping region.
262 | * @property {Matter.Bounds} body.plugin.wrap
263 | * @memberof Matter.Body
264 | */
265 |
266 | /**
267 | * This plugin adds a new property `composite.plugin.wrap` to instances of `Matter.Composite`.
268 | * This is a `Matter.Bounds` instance that specifies the wrapping region.
269 | * @property {Matter.Bounds} composite.plugin.wrap
270 | * @memberof Matter.Composite
271 | */
272 |
273 | /***/ })
274 | /******/ ]);
275 | });
--------------------------------------------------------------------------------
/js/lib/decomp.js:
--------------------------------------------------------------------------------
1 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.decomp=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o
=0 && s<=1 && t>=0 && t<=1);
61 | }
62 |
63 | /**
64 | * Get the area of a triangle spanned by the three given points. Note that the area will be negative if the points are not given in counter-clockwise order.
65 | * @static
66 | * @method area
67 | * @param {Array} a
68 | * @param {Array} b
69 | * @param {Array} c
70 | * @return {Number}
71 | */
72 | function triangleArea(a,b,c){
73 | return (((b[0] - a[0])*(c[1] - a[1]))-((c[0] - a[0])*(b[1] - a[1])));
74 | }
75 |
76 | function isLeft(a,b,c){
77 | return triangleArea(a,b,c) > 0;
78 | }
79 |
80 | function isLeftOn(a,b,c) {
81 | return triangleArea(a, b, c) >= 0;
82 | }
83 |
84 | function isRight(a,b,c) {
85 | return triangleArea(a, b, c) < 0;
86 | }
87 |
88 | function isRightOn(a,b,c) {
89 | return triangleArea(a, b, c) <= 0;
90 | }
91 |
92 | var tmpPoint1 = [],
93 | tmpPoint2 = [];
94 |
95 | /**
96 | * Check if three points are collinear
97 | * @method collinear
98 | * @param {Array} a
99 | * @param {Array} b
100 | * @param {Array} c
101 | * @param {Number} [thresholdAngle=0] Threshold angle to use when comparing the vectors. The function will return true if the angle between the resulting vectors is less than this value. Use zero for max precision.
102 | * @return {Boolean}
103 | */
104 | function collinear(a,b,c,thresholdAngle) {
105 | if(!thresholdAngle){
106 | return triangleArea(a, b, c) === 0;
107 | } else {
108 | var ab = tmpPoint1,
109 | bc = tmpPoint2;
110 |
111 | ab[0] = b[0]-a[0];
112 | ab[1] = b[1]-a[1];
113 | bc[0] = c[0]-b[0];
114 | bc[1] = c[1]-b[1];
115 |
116 | var dot = ab[0]*bc[0] + ab[1]*bc[1],
117 | magA = Math.sqrt(ab[0]*ab[0] + ab[1]*ab[1]),
118 | magB = Math.sqrt(bc[0]*bc[0] + bc[1]*bc[1]),
119 | angle = Math.acos(dot/(magA*magB));
120 | return angle < thresholdAngle;
121 | }
122 | }
123 |
124 | function sqdist(a,b){
125 | var dx = b[0] - a[0];
126 | var dy = b[1] - a[1];
127 | return dx * dx + dy * dy;
128 | }
129 |
130 | /**
131 | * Get a vertex at position i. It does not matter if i is out of bounds, this function will just cycle.
132 | * @method at
133 | * @param {Number} i
134 | * @return {Array}
135 | */
136 | function polygonAt(polygon, i){
137 | var s = polygon.length;
138 | return polygon[i < 0 ? i % s + s : i % s];
139 | }
140 |
141 | /**
142 | * Clear the polygon data
143 | * @method clear
144 | * @return {Array}
145 | */
146 | function polygonClear(polygon){
147 | polygon.length = 0;
148 | }
149 |
150 | /**
151 | * Append points "from" to "to"-1 from an other polygon "poly" onto this one.
152 | * @method append
153 | * @param {Polygon} poly The polygon to get points from.
154 | * @param {Number} from The vertex index in "poly".
155 | * @param {Number} to The end vertex index in "poly". Note that this vertex is NOT included when appending.
156 | * @return {Array}
157 | */
158 | function polygonAppend(polygon, poly, from, to){
159 | for(var i=from; i v[br][0])) {
175 | br = i;
176 | }
177 | }
178 |
179 | // reverse poly if clockwise
180 | if (!isLeft(polygonAt(polygon, br - 1), polygonAt(polygon, br), polygonAt(polygon, br + 1))) {
181 | polygonReverse(polygon);
182 | }
183 | }
184 |
185 | /**
186 | * Reverse the vertices in the polygon
187 | * @method reverse
188 | */
189 | function polygonReverse(polygon){
190 | var tmp = [];
191 | var N = polygon.length;
192 | for(var i=0; i!==N; i++){
193 | tmp.push(polygon.pop());
194 | }
195 | for(var i=0; i!==N; i++){
196 | polygon[i] = tmp[i];
197 | }
198 | }
199 |
200 | /**
201 | * Check if a point in the polygon is a reflex point
202 | * @method isReflex
203 | * @param {Number} i
204 | * @return {Boolean}
205 | */
206 | function polygonIsReflex(polygon, i){
207 | return isRight(polygonAt(polygon, i - 1), polygonAt(polygon, i), polygonAt(polygon, i + 1));
208 | }
209 |
210 | var tmpLine1=[],
211 | tmpLine2=[];
212 |
213 | /**
214 | * Check if two vertices in the polygon can see each other
215 | * @method canSee
216 | * @param {Number} a Vertex index 1
217 | * @param {Number} b Vertex index 2
218 | * @return {Boolean}
219 | */
220 | function polygonCanSee(polygon, a,b) {
221 | var p, dist, l1=tmpLine1, l2=tmpLine2;
222 |
223 | if (isLeftOn(polygonAt(polygon, a + 1), polygonAt(polygon, a), polygonAt(polygon, b)) && isRightOn(polygonAt(polygon, a - 1), polygonAt(polygon, a), polygonAt(polygon, b))) {
224 | return false;
225 | }
226 | dist = sqdist(polygonAt(polygon, a), polygonAt(polygon, b));
227 | for (var i = 0; i !== polygon.length; ++i) { // for each edge
228 | if ((i + 1) % polygon.length === a || i === a){ // ignore incident edges
229 | continue;
230 | }
231 | if (isLeftOn(polygonAt(polygon, a), polygonAt(polygon, b), polygonAt(polygon, i + 1)) && isRightOn(polygonAt(polygon, a), polygonAt(polygon, b), polygonAt(polygon, i))) { // if diag intersects an edge
232 | l1[0] = polygonAt(polygon, a);
233 | l1[1] = polygonAt(polygon, b);
234 | l2[0] = polygonAt(polygon, i);
235 | l2[1] = polygonAt(polygon, i + 1);
236 | p = lineInt(l1,l2);
237 | if (sqdist(polygonAt(polygon, a), p) < dist) { // if edge is blocking visibility to b
238 | return false;
239 | }
240 | }
241 | }
242 |
243 | return true;
244 | }
245 |
246 | /**
247 | * Copy the polygon from vertex i to vertex j.
248 | * @method copy
249 | * @param {Number} i
250 | * @param {Number} j
251 | * @param {Polygon} [targetPoly] Optional target polygon to save in.
252 | * @return {Polygon} The resulting copy.
253 | */
254 | function polygonCopy(polygon, i,j,targetPoly){
255 | var p = targetPoly || [];
256 | polygonClear(p);
257 | if (i < j) {
258 | // Insert all vertices from i to j
259 | for(var k=i; k<=j; k++){
260 | p.push(polygon[k]);
261 | }
262 |
263 | } else {
264 |
265 | // Insert vertices 0 to j
266 | for(var k=0; k<=j; k++){
267 | p.push(polygon[k]);
268 | }
269 |
270 | // Insert vertices i to end
271 | for(var k=i; k 0){
321 | return polygonSlice(polygon, edges);
322 | } else {
323 | return [polygon];
324 | }
325 | }
326 |
327 | /**
328 | * Slices the polygon given one or more cut edges. If given one, this function will return two polygons (false on failure). If many, an array of polygons.
329 | * @method slice
330 | * @param {Array} cutEdges A list of edges, as returned by .getCutEdges()
331 | * @return {Array}
332 | */
333 | function polygonSlice(polygon, cutEdges){
334 | if(cutEdges.length === 0){
335 | return [polygon];
336 | }
337 | if(cutEdges instanceof Array && cutEdges.length && cutEdges[0] instanceof Array && cutEdges[0].length===2 && cutEdges[0][0] instanceof Array){
338 |
339 | var polys = [polygon];
340 |
341 | for(var i=0; i maxlevel){
450 | console.warn("quickDecomp: max level ("+maxlevel+") reached.");
451 | return result;
452 | }
453 |
454 | for (var i = 0; i < polygon.length; ++i) {
455 | if (polygonIsReflex(poly, i)) {
456 | reflexVertices.push(poly[i]);
457 | upperDist = lowerDist = Number.MAX_VALUE;
458 |
459 |
460 | for (var j = 0; j < polygon.length; ++j) {
461 | if (isLeft(polygonAt(poly, i - 1), polygonAt(poly, i), polygonAt(poly, j)) && isRightOn(polygonAt(poly, i - 1), polygonAt(poly, i), polygonAt(poly, j - 1))) { // if line intersects with an edge
462 | p = getIntersectionPoint(polygonAt(poly, i - 1), polygonAt(poly, i), polygonAt(poly, j), polygonAt(poly, j - 1)); // find the point of intersection
463 | if (isRight(polygonAt(poly, i + 1), polygonAt(poly, i), p)) { // make sure it's inside the poly
464 | d = sqdist(poly[i], p);
465 | if (d < lowerDist) { // keep only the closest intersection
466 | lowerDist = d;
467 | lowerInt = p;
468 | lowerIndex = j;
469 | }
470 | }
471 | }
472 | if (isLeft(polygonAt(poly, i + 1), polygonAt(poly, i), polygonAt(poly, j + 1)) && isRightOn(polygonAt(poly, i + 1), polygonAt(poly, i), polygonAt(poly, j))) {
473 | p = getIntersectionPoint(polygonAt(poly, i + 1), polygonAt(poly, i), polygonAt(poly, j), polygonAt(poly, j + 1));
474 | if (isLeft(polygonAt(poly, i - 1), polygonAt(poly, i), p)) {
475 | d = sqdist(poly[i], p);
476 | if (d < upperDist) {
477 | upperDist = d;
478 | upperInt = p;
479 | upperIndex = j;
480 | }
481 | }
482 | }
483 | }
484 |
485 | // if there are no vertices to connect to, choose a point in the middle
486 | if (lowerIndex === (upperIndex + 1) % polygon.length) {
487 | //console.log("Case 1: Vertex("+i+"), lowerIndex("+lowerIndex+"), upperIndex("+upperIndex+"), poly.size("+polygon.length+")");
488 | p[0] = (lowerInt[0] + upperInt[0]) / 2;
489 | p[1] = (lowerInt[1] + upperInt[1]) / 2;
490 | steinerPoints.push(p);
491 |
492 | if (i < upperIndex) {
493 | //lowerPoly.insert(lowerPoly.end(), poly.begin() + i, poly.begin() + upperIndex + 1);
494 | polygonAppend(lowerPoly, poly, i, upperIndex+1);
495 | lowerPoly.push(p);
496 | upperPoly.push(p);
497 | if (lowerIndex !== 0){
498 | //upperPoly.insert(upperPoly.end(), poly.begin() + lowerIndex, poly.end());
499 | polygonAppend(upperPoly, poly,lowerIndex,poly.length);
500 | }
501 | //upperPoly.insert(upperPoly.end(), poly.begin(), poly.begin() + i + 1);
502 | polygonAppend(upperPoly, poly,0,i+1);
503 | } else {
504 | if (i !== 0){
505 | //lowerPoly.insert(lowerPoly.end(), poly.begin() + i, poly.end());
506 | polygonAppend(lowerPoly, poly,i,poly.length);
507 | }
508 | //lowerPoly.insert(lowerPoly.end(), poly.begin(), poly.begin() + upperIndex + 1);
509 | polygonAppend(lowerPoly, poly,0,upperIndex+1);
510 | lowerPoly.push(p);
511 | upperPoly.push(p);
512 | //upperPoly.insert(upperPoly.end(), poly.begin() + lowerIndex, poly.begin() + i + 1);
513 | polygonAppend(upperPoly, poly,lowerIndex,i+1);
514 | }
515 | } else {
516 | // connect to the closest point within the triangle
517 | //console.log("Case 2: Vertex("+i+"), closestIndex("+closestIndex+"), poly.size("+polygon.length+")\n");
518 |
519 | if (lowerIndex > upperIndex) {
520 | upperIndex += polygon.length;
521 | }
522 | closestDist = Number.MAX_VALUE;
523 |
524 | if(upperIndex < lowerIndex){
525 | return result;
526 | }
527 |
528 | for (var j = lowerIndex; j <= upperIndex; ++j) {
529 | if (isLeftOn(polygonAt(poly, i - 1), polygonAt(poly, i), polygonAt(poly, j)) && isRightOn(polygonAt(poly, i + 1), polygonAt(poly, i), polygonAt(poly, j))) {
530 | d = sqdist(polygonAt(poly, i), polygonAt(poly, j));
531 | if (d < closestDist) {
532 | closestDist = d;
533 | closestIndex = j % polygon.length;
534 | }
535 | }
536 | }
537 |
538 | if (i < closestIndex) {
539 | polygonAppend(lowerPoly, poly,i,closestIndex+1);
540 | if (closestIndex !== 0){
541 | polygonAppend(upperPoly, poly,closestIndex,v.length);
542 | }
543 | polygonAppend(upperPoly, poly,0,i+1);
544 | } else {
545 | if (i !== 0){
546 | polygonAppend(lowerPoly, poly,i,v.length);
547 | }
548 | polygonAppend(lowerPoly, poly,0,closestIndex+1);
549 | polygonAppend(upperPoly, poly,closestIndex,i+1);
550 | }
551 | }
552 |
553 | // solve smallest poly first
554 | if (lowerPoly.length < upperPoly.length) {
555 | polygonQuickDecomp(lowerPoly,result,reflexVertices,steinerPoints,delta,maxlevel,level);
556 | polygonQuickDecomp(upperPoly,result,reflexVertices,steinerPoints,delta,maxlevel,level);
557 | } else {
558 | polygonQuickDecomp(upperPoly,result,reflexVertices,steinerPoints,delta,maxlevel,level);
559 | polygonQuickDecomp(lowerPoly,result,reflexVertices,steinerPoints,delta,maxlevel,level);
560 | }
561 |
562 | return result;
563 | }
564 | }
565 | result.push(polygon);
566 |
567 | return result;
568 | }
569 |
570 | /**
571 | * Remove collinear points in the polygon.
572 | * @method removeCollinearPoints
573 | * @param {Number} [precision] The threshold angle to use when determining whether two edges are collinear. Use zero for finest precision.
574 | * @return {Number} The number of points removed
575 | */
576 | function polygonRemoveCollinearPoints(polygon, precision){
577 | var num = 0;
578 | for(var i=polygon.length-1; polygon.length>3 && i>=0; --i){
579 | if(collinear(polygonAt(polygon, i-1),polygonAt(polygon, i),polygonAt(polygon, i+1),precision)){
580 | // Remove the middle point
581 | polygon.splice(i%polygon.length,1);
582 | num++;
583 | }
584 | }
585 | return num;
586 | }
587 |
588 | /**
589 | * Check if two scalars are equal
590 | * @static
591 | * @method eq
592 | * @param {Number} a
593 | * @param {Number} b
594 | * @param {Number} [precision]
595 | * @return {Boolean}
596 | */
597 | function scalar_eq(a,b,precision){
598 | precision = precision || 0;
599 | return Math.abs(a-b) < precision;
600 | }
601 |
602 | },{}]},{},[1])
603 | (1)
604 | });
605 |
--------------------------------------------------------------------------------
/js/simple-svg-physics-runner.js:
--------------------------------------------------------------------------------
1 | /*
2 | simple-svg-physics-runner
3 |
4 | 2024.02.22, v.1.2.0b2
5 |
6 | * Copyright(c) 2018 Hiroyuki Sato
7 | https://github.com/shspage/simple-svg-physics-runner
8 | This software is distributed under the MIT License.
9 | See the LICENSE.txt for details.
10 |
11 | This software uses the following libraries that may have licenses
12 | differing from that of the software itself. You can find the
13 | libraries and their respective licenses below.
14 | -------------------------------------------
15 | required libraries (and the version tested with)
16 | * jQuery (v3.7.1)
17 | License MIT
18 | (c) OpenJS Foundation and other contributors | jquery.org/license
19 |
20 | * matter-js (0.19.0) http://brm.io/matter-js/
21 | License MIT
22 | Copyright (c) Liam Brummitt and contributors.
23 |
24 | * Paper.js (v0.12.17) http://paperjs.org/
25 | License MIT
26 | Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey
27 | http://juerglehni.com/ & https://puckey.studio/
28 |
29 | * decomp.js (https://github.com/schteppe/poly-decomp.js)
30 | License MIT
31 | Copyright (c) 2013 Stefan Hedman
32 |
33 | */
34 |
35 | (function () {
36 | 'use strict';
37 | Matter.use('matter-wrap');
38 | console.log("@shspage: The wrap plugin appears to be working despite the required version unmatch");
39 |
40 | const EXPORT_SVG_FILENAME = "output.svg";
41 | const CANVAS_BACKGROUND_COLOR = "#fafafa";
42 | const BODY_DENSITY = 0.12; // default=0.001
43 | const FRICTIONAIR_DEFAULT = 0.01; // 空気抵抗の既定値
44 | const FRICTIONAIR_HIGH = 0.8; // 空気抵抗=重い の値
45 | const GRAVITY_Y_DEFAULT = 1; // 重力=通常 の値
46 | const RESTITUTION_DEFAULT = 0.35; // 反発の既定値
47 |
48 | // Matter.Engine properties
49 | // * The higher the value, the higher quality the simulation will be at the expense of performance.
50 | // * 各値を増やすことでパフォーマンスと引き換えにシミュレーションの品質を上げることができる。
51 | // 各値がどのように影響するかは、Demo頁の右から引き出せるスライダ群を操作してみると手掛かりになるかも。
52 | const ENGINE_POSITION_ITERATIONS = 12; // positionIterations, default=6
53 | const ENGINE_VELOCITY_ITERATIONS = 4; // velocityIterations, default=4
54 | const ENGINE_CONSTRAINT_ITERATIONS = 2; // constraintIterations, default=2
55 |
56 | const DECOMP_MINIMUM_AREA = 0; // 複合パスを図形に分解した際に削除する図形の最大面積
57 | const DUPLICATE_POINT_TOLERANCE = 0.01; // 複合パスの重複点を取り除くときの許容差
58 | const REMOVE_COLLINEAR_PRECISION = 0.01; // 頂点が直線上にあると見なす許容差
59 | const FLATTEN_FLATNESS = 0.25; // 曲線を折れ線化する際の許容誤差
60 |
61 | // AS_BLACK_THRESHOLD_RGB:
62 | // * In Illustrator, black in CMYK mode is not converted to #000 in generated SVG code.
63 | // This means the object doesn't stay on fixed position.
64 | // So, set the upper limit of RGB values that is considered black. (0-255 = 0-1)
65 | // If all RGB values are below the upper limit, it will be converted to #000,
66 | // and it will also be #000 when exporting.
67 | // If you ignore CMYK mode, it's OK to set this [0,0,0].
68 | // * イラレのCMYKモードでの黒(K100)は、生成されるSVGコードでは#000にならない。
69 | // つまり位置が固定されない。
70 | // 以下で黒と見なすRGBそれぞれの上限値を設定しておくと(0~255 を 0~1 とする)、
71 | // RGB全てが上限値以下の場合に#000に変換され、エクスポート時も#000になる。
72 | // CMYKモードを考慮しない場合は [0,0,0] に設定してもよい。
73 | const AS_BLACK_THRESHOLD_RGB = [0.1373, 0.0942, 0.0824]; // about #231815
74 |
75 | paper.setup("hidden_canvas");
76 |
77 | var Body = Matter.Body,
78 | Bodies = Matter.Bodies,
79 | Vertices = Matter.Vertices,
80 | Vector = Matter.Vector,
81 | Mouse = Matter.Mouse,
82 | MouseConstraint = Matter.MouseConstraint,
83 | Render = Matter.Render,
84 | Runner = Matter.Runner,
85 | Constraint = Matter.Constraint,
86 | Composites = Matter.Composites,
87 | Composite = Matter.Composite,
88 | Engine = Matter.Engine,
89 | Common = Matter.Common;
90 |
91 | var _spec = {
92 | url:null,
93 | isPause:false,
94 | wireframes: false,
95 | gravity_y: 0,
96 | frictionAir:FRICTIONAIR_DEFAULT,
97 | _decomp_fail_count:0
98 | }
99 |
100 | // * Ratio to consider pass as circle (Larger ÷ Smaller)
101 | // bounds: width to height ratio
102 | // length, area : ratio between "the perimeter and area calculated using
103 | // the width as diameter" and "actual value"
104 | // * パスを円と見なす比率(大きい方÷小さい方)
105 | // bounds: 幅と高さの比率
106 | // perimeter, area : 幅を直径として算出した円の周長・面積と、実際の値との比率
107 | var _circleRatioLimit = {
108 | bounds : 1.02,
109 | perimeter : 1.05,
110 | area : 1.02
111 | }
112 |
113 | var _messages;
114 |
115 | var _engine;
116 | var _runner;
117 | var _render;
118 |
119 | var _svgShapes = {}; // { body.id : paper.Item }
120 | var _svgDataNames = {}; // { id : data-name }
121 |
122 | function setupWorld(items){
123 | _engine = Engine.create({
124 | positionIterations: ENGINE_POSITION_ITERATIONS,
125 | velocityIterations : ENGINE_VELOCITY_ITERATIONS,
126 | constraintIterations : ENGINE_CONSTRAINT_ITERATIONS
127 | });
128 | _engine.world.gravity.y = _spec.gravity_y;
129 |
130 | _render = Render.create({
131 | element: document.body,
132 | engine: _engine,
133 | options: {
134 | background: CANVAS_BACKGROUND_COLOR,
135 | width: window.innerWidth,
136 | height: window.innerHeight,
137 | showVelocity: false,
138 | wireframes: _spec.wireframes
139 | }
140 | });
141 |
142 | Render.run(_render);
143 |
144 | _runner = Runner.create();
145 | Runner.run(_runner, _engine);
146 |
147 | var mouse = Mouse.create(_render.canvas),
148 | mouseConstraint = MouseConstraint.create(_engine, {
149 | element: document.body,
150 | constraint: {
151 | stiffness: 0.2,
152 | render: {
153 | visible: false
154 | }
155 | }
156 | });
157 |
158 | Composite.add(_engine.world, mouseConstraint);
159 | Composite.add(_engine.world, items);
160 |
161 | _render.mouse = mouse;
162 |
163 | Render.lookAt(_render, {
164 | min: { x: 0, y: 0 },
165 | max: { x: window.innerWidth, y: window.innerHeight }
166 | });
167 | }
168 |
169 | // ----------------------
170 | // functions to load data
171 | // ----------------------
172 | function checkLoaded(){
173 | if(_engine){
174 | alert(getMessage("already_loaded"));
175 | return true;
176 | }
177 | return false;
178 | }
179 |
180 | // drag and drop
181 | function handleFileSelect(evt){
182 | evt.stopPropagation();
183 | evt.preventDefault();
184 | if(checkLoaded()) return;
185 |
186 | var files = evt.dataTransfer.files;
187 | if(files.length > 0){
188 | var fileobj = files[0];
189 | var type = fileobj.type;
190 | if(type == "image/svg+xml"){
191 | var fr = new FileReader();
192 | fr.onload = function(e){
193 | importSVG_paper(e.target.result);
194 | }
195 | fr.readAsText(fileobj, "utf-8");
196 | } else {
197 | if(type == "") type = "(unknown)";
198 | alert(getMessage("please_select_a_svg") + type + ").");
199 | }
200 | }
201 | }
202 |
203 | function handleDragOver(evt){
204 | evt.stopPropagation();
205 | evt.preventDefault();
206 | evt.dataTransfer.dropEffect = "copy";
207 | }
208 |
209 | // chrome 版。firefoxは追って対応かも(textareaを介して読み込む形になる)
210 | function importSVGFromClipboard_paper(){
211 | if(checkLoaded()) return;
212 | if(navigator.clipboard){
213 | navigator.clipboard.readText()
214 | .then((data) => importSVG_paper(data))
215 | .catch((e) => alert(getMessage("paste_failed")));
216 | } else {
217 | alert(getMessage("paste_chrome_only_for_now"));
218 | }
219 | }
220 |
221 | function importSVG_paper(data){
222 | try{
223 | _spec._decomp_fail_count = 0;
224 |
225 | var rex = /\<\w+ id=\"(\w+)\" data-name=\"([^\"]+)\"/g;
226 | var m;
227 | while((m = rex.exec(data)) !== null){
228 | _svgDataNames[m[1]] = m[2];
229 | }
230 |
231 | paper.project.view.viewSize = new paper.Size(
232 | window.innerWidth, window.innerHeight);
233 |
234 | paper.project.importSVG(data);
235 | fixTopLeftAfterImport();
236 | paper2matter();
237 |
238 | var msg = "LOADED";
239 | if(_spec._decomp_fail_count > 0){
240 | msg += " / decomp error (" + _spec._decomp_fail_count + ")";
241 | }
242 | alert(msg);
243 | } catch(e){
244 | alert(e);
245 | }
246 | }
247 |
248 | function fixTopLeftAfterImport(){
249 | var items = paper.project.activeLayer.children;
250 |
251 | if(items.length > 0){
252 | var top = items[0].bounds.top;
253 | var left = items[0].bounds.left;
254 |
255 | for(var i = 1, iEnd = items.length; i < iEnd; i++){
256 | top = Math.min(top, items[i].bounds.top);
257 | left = Math.min(left, items[i].bounds.left);
258 | }
259 |
260 | if(top != 0 || left != 0){
261 | var delta = new paper.Point(-left, -top);
262 | for(var i = 0, iEnd = items.length; i < iEnd; i++){
263 | items[i].translate(delta);
264 | }
265 | }
266 | }
267 | }
268 |
269 | // ----------------------
270 | // functions to convert data, from paper.js to Matter.js
271 | // ----------------------
272 | // * Load SVG data from clipboard or file. Using "importSVG" of paper.js.
273 | // Matter.js also has SVG import method, but I couldn't find a sample
274 | // code to get color of each shape in SVG data. Since there may be
275 | // various format in SVG data, I thought that it is easier to handle
276 | // by putting it in the paper.js class rather than writing the analyzing
277 | // process by myself. For this reason, at here, the Paths loaded with
278 | // paper.js are converted to Body object of Matter.js.
279 | // Paths are kept in the background and used when exporting.
280 | // * クリップボードまたはファイルからのSVGデータ取り込み。
281 | // paper.js の importSVG で行う。Matter.js にも SVG 取り込みの
282 | // メソッドはあるが、サンプルコードにあるのは形状を取り込む処理
283 | // だけで、色を取得する処理がない。SVG のデータも色々なものが
284 | // あるので、自力で解析する処理を書くより、paper.js のクラスに
285 | // 落とし込んだほうが扱いやすいと考えた。
286 | // このためここでは、paper.js で取り込んだ Path を Matter.js の
287 | // Body に変換する処理をしている。
288 | // Path はバックグラウンドで保持され、エクスポート時に使われる。
289 |
290 | function paper2matter(){
291 | var items = [];
292 | extractItems(paper.project.activeLayer.children, items, "");
293 | setupWorld(items);
294 | }
295 |
296 | function extractItems(children, items, parent_name){
297 | var grp, grp_type, is_wrap, coll_group;
298 | if(parent_name){
299 | if(parent_name.startsWith("chain")){
300 | grp = Composite.create();
301 | grp_type = "chain";
302 | } else if(parent_name.startsWith("bridge")){
303 | grp = Composite.create();
304 | grp_type = "bridge";
305 | coll_group = Body.nextGroup(true);
306 | } else if(parent_name.startsWith("loop")){
307 | grp = Composite.create();
308 | grp_type = "loop";
309 | coll_group = Body.nextGroup(true);
310 | }
311 |
312 | is_wrap = parent_name.includes("wrap");
313 | } else {
314 | parent_name = "";
315 | }
316 |
317 | for(var i = children.length - 1; i >= 0; i--){
318 | var c = children[i];
319 | var body;
320 |
321 | var cname = c.name || "";
322 | if(cname != "" && cname in _svgDataNames){
323 | cname = _svgDataNames[cname];
324 | }
325 |
326 | if(c.className == 'Layer'){
327 | extractItems(c.children, items, cname);
328 | } else if(c.className == 'Group'){
329 | extractItems(c.children, items, cname + "." + parent_name);
330 | } else if(c.clipMask){
331 | c.remove();
332 | } else if(c.className == "Shape" || c.className == "Path" || c.className == "CompoundPath"){
333 | if(c.className == "CompoundPath"){
334 | if(!coll_group) coll_group = Body.nextGroup(true);
335 | body = createCompoundBody(c, coll_group);
336 | } else if(c.shape == "rectangle"){
337 | body = createRectangle(c);
338 | } else if(c.shape == "circle" || isCircle(c)){
339 | body = createCircle(c);
340 | } else if(c.shape == "ellipse"){
341 | var tmp_path = c.toPath();
342 | body = createPolygon(tmp_path);
343 | tmp_path.remove();
344 | } else if(c.segments){
345 | if(c.closed){
346 | body = createPolygon(c);
347 | }
348 | }
349 | if(!body) continue;
350 |
351 | if(grp_type == "chain" || grp_type == "bridge" || grp_type == "loop"){
352 | if(coll_group) body.collisionFilter.group = coll_group;
353 | Composite.addBody(grp, body);
354 | } else {
355 | if(!hangBody(cname, parent_name, body, items)){
356 | if(is_wrap) addWrap(body);
357 | }
358 | items.push(body);
359 | }
360 | _svgShapes[body.id] = c;
361 | }
362 | }
363 |
364 | if(grp_type == "chain"){
365 | createChain(grp, parent_name);
366 | items.push(grp);
367 | } else if(grp_type == "bridge"){
368 | createBridge(grp);
369 | items.push(grp);
370 | } else if(grp_type == "loop"){
371 | if(is_wrap) addWrap(grp);
372 | // create outer constraints
373 | grp.bodies = sortBodiesByNearest(grp.bodies);
374 | var stiffness = 0.2;
375 | createLoop(grp, stiffness);
376 | // create inner constraints
377 | if(grp.bodies.length > 3){
378 | grp.bodies = sortBodiesByNearest(grp.bodies, true);
379 | var ignoreLast = true;
380 | stiffness = 0.2;
381 | createLoop(grp, stiffness, ignoreLast);
382 | }
383 | items.push(grp);
384 | }
385 | }
386 |
387 | function addWrap(body){ // body or composite
388 | body.plugin.wrap = {
389 | min: {
390 | x: 0,
391 | y: 0
392 | },
393 | max: {
394 | x: window.innerWidth,
395 | y: window.innerWidth
396 | }
397 | };
398 | }
399 |
400 | function hangBody(cname, parent_name, body, items){
401 | var hanged = false;
402 | if(body.isStatic) return hanged;
403 | var name = cname + "." + parent_name;
404 | var m = name.match(/hang([0-9]+)/);
405 | if(m){
406 | hanged = true;
407 | var len = m[1];
408 | //Body.setMass(body, 100);
409 | //body.restitution = 1;
410 | //Body.setInertia(body, Infinity);
411 | //body.friction = 0;
412 |
413 | var height = body.bounds.max.y - body.bounds.min.y;
414 |
415 | var offset = 0;
416 | m = name.match(/offset(-?0\.[0-5])/i);
417 | if(m){
418 | offset = height * m[1];
419 | }
420 |
421 | var constraint = Constraint.create({
422 | pointA: { x: body.position.x, y: body.position.y - len},
423 | bodyB: body,
424 | pointB: { x: 0, y: offset },
425 | });
426 |
427 | if(name.includes("hidden")){
428 | constraint.render.visible = false;
429 | } else {
430 | var strokeColor = "#000";
431 | m = name.match(/#[0-9a-f]{6}/i);
432 | if(m){
433 | strokeColor = m[0];
434 | } else {
435 | m = name.match(/#[0-9a-f]{3}/i);
436 | if(m){
437 | strokeColor = m[0];
438 | }
439 | }
440 | constraint.render.type = "line";
441 | constraint.render.strokeStyle = strokeColor;
442 | constraint.render.lineWidth = 1;
443 | constraint.render.anchors = false;
444 | constraint.label = "hang";
445 | }
446 | items.push(constraint);
447 | }
448 | return hanged;
449 | }
450 |
451 | function createChain(grp, parent_name){
452 | // sort bodies from top to bottom. the pivot of chain is placed at the top body.
453 | var as_is = parent_name.includes(" as is");
454 | if(grp.bodies.length > 1){
455 | grp.bodies.sort(function(a,b){ return a.position.y - b.position.y; });
456 | if(as_is){
457 | for(var i = 0, iEnd = grp.bodies.length - 1; i < iEnd; i++){
458 | var b = grp.bodies[i];
459 | var b1 = grp.bodies[i + 1]
460 | Composite.add(grp, Constraint.create({
461 | bodyA: b,
462 | bodyB: b1,
463 | length: Vector.magnitude(Vector.sub(b.position, b1.position)),
464 | stiffness: 0.99,
465 | render: { visible:false }
466 | }))
467 | }
468 | } else {
469 | Composites.chain(grp, 0, 0.5, 0, -0.5, { stiffness: 0.99, length: 1, render: { visible:false } });
470 | // 最後の constraint の動作が固い。原因は今のところ不明。
471 | grp.constraints[grp.constraints.length - 1].stiffness = 0.66;
472 | }
473 | }
474 | if(true){
475 | var b = grp.bodies[0];
476 | var bHeight = b.bounds.max.y - b.bounds.min.y
477 | Composite.add(grp, Constraint.create({
478 | bodyB: b,
479 | pointB: { x: 0, y: -bHeight / 2 },
480 | pointA: { x: b.position.x, y: b.position.y - bHeight / 2},
481 | stiffness: 1,
482 | render: { visible:false }
483 | }));
484 | }
485 | }
486 |
487 | function createBridge(grp){
488 | // sort bodies horizontaly. the pivot of chain is placed at both ends of bodies.
489 | if(grp.bodies.length < 1) return;
490 |
491 | if(grp.bodies.length > 1){
492 | grp.bodies.sort(function(a,b){ return a.position.x - b.position.x; });
493 | }
494 | var left = grp.bodies[0].bounds.min.x;
495 | var right = grp.bodies[grp.bodies.length - 1].bounds.max.x;
496 | if(grp.bodies.length > 1){
497 | Composites.chain(grp, 0.5, 0, -0.5, 0, { stiffness: 0.99, length: 0.0001, render: { visible: false } });
498 | // 最後の constraint の動作が固い。原因は今のところ不明。
499 | grp.constraints[grp.constraints.length - 1].stiffness = 0.66;
500 | }
501 | var b = grp.bodies[0];
502 | var bWidth = b.bounds.max.x - b.bounds.min.x;
503 | Composite.add(grp, Constraint.create({
504 | bodyB: b,
505 | pointB: { x: -bWidth / 2, y: 0 },
506 | pointA: { x: left, y: b.position.y },
507 | length: 2,
508 | stiffness: 0.9,
509 | render: { visible: false }
510 | }));
511 | b = grp.bodies[grp.bodies.length - 1];
512 | bWidth = b.bounds.max.x - b.bounds.min.x;
513 | Composite.add(grp, Constraint.create({
514 | bodyB: b,
515 | pointB: { x: bWidth / 2, y: 0 },
516 | pointA: { x: right, y: b.position.y },
517 | length: 2,
518 | stiffness: 0.9,
519 | render: { visible: false }
520 | }));
521 | }
522 |
523 | function createLoop(grp, stiffness, ignoreLast){
524 | // ignoreLast: if true, ignore last body
525 | var bLength = grp.bodies.length;
526 | if(bLength == 1) return;
527 | for(var i = 0; i < bLength; i++){
528 | if(ignoreLast && i == bLength - 1) break;
529 | var b = grp.bodies[i];
530 | var nextIndex = i == bLength - 1 ? 0 : i + 1;
531 | var b1 = grp.bodies[nextIndex];
532 | Composite.add(grp, Constraint.create({
533 | bodyA: b,
534 | bodyB: b1,
535 | length: Vector.magnitude(Vector.sub(b.position, b1.position)),
536 | stiffness: stiffness,
537 | //render: { type: 'line' }
538 | render: { visible:false }
539 | }))
540 | }
541 | }
542 |
543 | function sortBodiesByNearest(bodies, farthest){
544 | // if farthest == true, sorts by farthest
545 | if(bodies.length < 3) return bodies;
546 | var bs = [bodies[0]];
547 | bodies.splice(0, 1);
548 | while(bodies.length > 0){
549 | var b = bs[bs.length - 1];
550 | var min_dist = -1, nearest_idx;
551 | for(var i = 0, iEnd = bodies.length; i < iEnd; i++){
552 | var d = Vector.magnitudeSquared(Vector.sub(b.position, bodies[i].position));
553 | if(farthest){
554 | if(min_dist < 0 || d > min_dist){
555 | min_dist = d;
556 | nearest_idx = i;
557 | }
558 | } else {
559 | if(min_dist < 0 || d < min_dist){
560 | min_dist = d;
561 | nearest_idx = i;
562 | }
563 | }
564 | }
565 | bs.push(bodies[nearest_idx]);
566 | bodies.splice(nearest_idx, 1);
567 | }
568 | return bs;
569 | }
570 |
571 | function isCircle(item){
572 | var b = item.bounds;
573 | var boundsRatio = calcRatio(item.bounds.width, item.bounds.height);
574 | var perimeterRatio = calcRatio(item.length, item.bounds.width * Math.PI);
575 | var areaRatio = calcRatio(item.area, Math.pow(item.bounds.width / 2, 2) * Math.PI);
576 |
577 | return boundsRatio < _circleRatioLimit.bounds
578 | && perimeterRatio < _circleRatioLimit.perimeter
579 | && areaRatio < _circleRatioLimit.area;
580 | }
581 |
582 | function calcRatio(a,b){
583 | var v1, v2;
584 | if(a > b){
585 | v1 = a; v2 = b;
586 | } else {
587 | v1 = b; v2 = a;
588 | }
589 | if(v2 == 0) return Infinity;
590 | return v1/v2;
591 | }
592 |
593 | function createCircle(item){
594 | var b = item.bounds;
595 | return Bodies.circle(b.center.x, b.center.y, b.width / 2, getStyle(item));
596 | }
597 |
598 | function createRectangle(item){
599 | var b = item.bounds;
600 | var s = item.size;
601 | var body = Bodies.rectangle(b.center.x, b.center.y, s.width, s.height, getStyle(item));
602 | if(item.rotation){
603 | var angle = item.rotation * Math.PI / 180;
604 | Body.rotate(body, angle);
605 | }
606 | return body;
607 | }
608 |
609 | function createPolygon(c){
610 | var center = c.bounds.center;
611 | var c1 = c.clone();
612 | c1.flatten(FLATTEN_FLATNESS);
613 | var vertices = getPoints(c1, center);
614 | c1.remove();
615 | removeDuplicatePoints(vertices);
616 |
617 | var options = getStyle(c);
618 | var body;
619 | if(!Vertices.isConvex(vertices)){
620 | var parts = [];
621 | var decomped_parts = [];
622 | decompPolygon(vertices, decomped_parts);
623 | if(decomped_parts.length > 0){
624 | for (var i = 0, iEnd = decomped_parts.length; i < iEnd; i++) {
625 | var part_body = Body.create(Common.extend(decomped_parts[i], options));
626 | parts.push(part_body);
627 | }
628 | body = Body.create(options);
629 | Body.setParts(body, parts);
630 | Body.translate(body, center);
631 | }
632 | } else {
633 | body = Bodies.fromVertices(center.x, center.y, vertices, options);
634 | }
635 | return body;
636 | }
637 |
638 | function createCompoundBody(c, coll_group){
639 | var center = c.bounds.center;
640 | var parts = [];
641 | var parts_blank = [];
642 | var options = {
643 | density : BODY_DENSITY,
644 | frictionAir : _spec.frictionAir,
645 | restitution : RESTITUTION_DEFAULT,
646 | isStatic : isFilledBlack(c),
647 | collisionFilter: { group: coll_group },
648 | render : { fillStyle: CANVAS_BACKGROUND_COLOR }
649 | };
650 | var c1 = c.clone();
651 | c1.flatten(FLATTEN_FLATNESS);
652 | for(var ci = 0, ciEnd = c.children.length; ci < ciEnd; ci++){
653 | var child = c1.children[ci];
654 | var parts_tmp, part_label;
655 | var n = countContainsEx(ci, c.children);
656 | if(n % 2 == 0){
657 | options.render.fillStyle = color2css(c.fillColor, CANVAS_BACKGROUND_COLOR);
658 | parts_tmp = parts;
659 | part_label = "";
660 | } else {
661 | options.render.fillStyle = CANVAS_BACKGROUND_COLOR;
662 | parts_tmp = parts_blank;
663 | part_label = "blank";
664 | }
665 | var vertices = getPoints(child, center);
666 | removeDuplicatePoints(vertices);
667 |
668 | if(!Vertices.isConvex(vertices)){
669 | var decomped_parts = [];
670 | decompPolygon(vertices, decomped_parts);
671 | for (var i = 0, iEnd = decomped_parts.length; i < iEnd; i++) {
672 | var body = Body.create(Common.extend(decomped_parts[i], options));
673 | body.label = part_label;
674 | parts_tmp.push(body);
675 | }
676 | } else {
677 | var body = Body.create(Common.extend({
678 | position: Vertices.centre(vertices),
679 | vertices: vertices
680 | }, options));
681 | body.label = part_label;
682 | parts_tmp.push(body);
683 | }
684 | }
685 | c1.remove();
686 |
687 | var parts_all = parts.concat(parts_blank);
688 | var compound;
689 | if(parts_all.length > 0){
690 | compound = Body.create({
691 | density : BODY_DENSITY,
692 | frictionAir : _spec.frictionAir,
693 | restitution : RESTITUTION_DEFAULT,
694 | isStatic : isFilledBlack(c) });
695 | Body.setParts(compound, parts_all);
696 | Body.translate(compound, center);
697 | //Body.setPosition(compound, center);
698 | }
699 | return compound;
700 | }
701 |
702 | // decomp processing referred to Bodies.fromVertices in matter.js
703 | function decompPolygon(vertices, decomped_parts){
704 | if(decomp && decomp.quickDecomp){
705 | var concave = vertices.map(function(vertex) {
706 | return [vertex.x, vertex.y];
707 | });
708 | decomp.makeCCW(concave);
709 | decomp.removeCollinearPoints(concave, REMOVE_COLLINEAR_PRECISION);
710 | var decomposed = decomp.quickDecomp(concave);
711 |
712 | if(decomposed.length < 1){
713 | console.log("decomp failed");
714 | _spec._decomp_fail_count++;
715 | return;
716 | }
717 |
718 | for (var i = 0, iEnd = decomposed.length; i < iEnd; i++) {
719 | var chunk = decomposed[i];
720 |
721 | // convert vertices into the correct structure
722 | var chunkVertices = chunk.map(function(vertices) {
723 | return {
724 | x: vertices[0],
725 | y: vertices[1]
726 | };
727 | });
728 |
729 | // skip small chunks
730 | if (DECOMP_MINIMUM_AREA > 0 && Vertices.area(chunkVertices) < DECOMP_MINIMUM_AREA)
731 | continue;
732 |
733 | // create a compound part
734 | decomped_parts.push({
735 | position: Vertices.centre(chunkVertices),
736 | vertices: chunkVertices
737 | });
738 | }
739 | }
740 | }
741 |
742 | function countContainsEx(ci, children){
743 | var n0 = countContains(ci, children, 0);
744 | var n1 = countContains(ci, children, parseInt(children[ci].segments.length / 2));
745 | return Math.min(n0, n1);
746 | }
747 |
748 | function countContains(ci, children, idx){
749 | // * A simple check to see how many shapes that include
750 | // children[ci] inside the elements (children) of compound path.
751 | // Assume that there are no intersections of elements.
752 | // If the result is an odd number, consider children[ci] to be a hole.
753 | // compound path の要素(children)の中に、
754 | // children[ci] を内側に含む図形がいくつあるか
755 | // の簡易的なチェック。要素の交差はないと仮定。
756 | // 結果が奇数の場合、children[ci] は hole と考える。
757 | var n = 0;
758 | var p = children[ci].segments[idx].point;
759 | for(var i = 0, iEnd = children.length; i < iEnd; i++){
760 | if(i == ci) continue;
761 | if(children[i].contains(p)){
762 | n++;
763 | }
764 | }
765 | return n;
766 | }
767 |
768 | function removeDuplicatePoints(points){
769 | function checkDuplicatePoint(p, p1){
770 | return Math.abs(p.x - p1.x) < DUPLICATE_POINT_TOLERANCE
771 | && Math.abs(p.y - p1.y) < DUPLICATE_POINT_TOLERANCE;
772 | }
773 | var idx = points.length - 1;
774 | var p = points[idx];
775 | var p1;
776 | while(idx > 0){
777 | idx--;
778 | p1 = points[idx];
779 | if(checkDuplicatePoint(p, p1)){
780 | points.splice(idx, 1);
781 | } else {
782 | p = p1;
783 | }
784 | }
785 | if(points.length > 1){
786 | p = points[points.length - 1];
787 | p1 = points[0];
788 | if(checkDuplicatePoint(p, p1)){
789 | points.splice(0, 1);
790 | }
791 | }
792 | }
793 |
794 | function isFilledBlack(item){
795 | var result = false;
796 | var fc = item.fillColor;
797 | //return (fc && fc.red == 0 && fc.green == 0 && fc.blue == 0);
798 | var rgb = AS_BLACK_THRESHOLD_RGB;
799 | if(fc && fc.red <= rgb[0] && fc.green <= rgb[1] && fc.blue <= rgb[2]){
800 | fc.red = 0; fc.green = 0; fc.blue = 0;
801 | result = true;
802 | }
803 | return result;
804 | }
805 |
806 | function color2css(col, nullValue){
807 | return col ? col.toCSS() : nullValue;
808 | }
809 |
810 | function getStyle(item){
811 | // ・塗り=null にするとデフォルト色(ランダムカラー)が割り当てられる模様
812 | // なので、塗りなしの場合、背景色を設定している。
813 | return {
814 | density : BODY_DENSITY,
815 | frictionAir : _spec.frictionAir,
816 | restitution : RESTITUTION_DEFAULT,
817 | isStatic : isFilledBlack(item),
818 | render : {
819 | fillStyle: color2css(item.fillColor, CANVAS_BACKGROUND_COLOR),
820 | strokeStyle : color2css(item.strokeColor, null),
821 | lineWidth : item.strokeColor ? item.strokeWidth : 0,
822 | opacity : item.opacity
823 | }
824 | }
825 | }
826 |
827 | function getPoints(item, center){
828 | var segs = item.segments;
829 | var r = [];
830 | for(var i = 0, iEnd = segs.length; i < iEnd; i++){
831 | r.push(segs[i].point.subtract(center));
832 | }
833 | return r;
834 | }
835 |
836 | /* function getCentroid(item){
837 | var segs = item.segments;
838 | var p = segs[0].point.clone();
839 | for(var i = 1, iEnd = segs.length; i < iEnd; i++){
840 | p.x += segs[i].point.x;
841 | p.y += segs[i].point.y;
842 | }
843 | return p.multiply(1 / segs.length);
844 | } */
845 |
846 | // ----------------------
847 | // functions to output
848 | // ----------------------
849 | // body.id に関連づけた、paper.js でimportした元の形状を、
850 | // 別レイヤーにコピーし、body の移動・回転を適用したうえで exportSVG する。
851 | function exportSVGtoFile(){
852 | if(confirm(getMessage("export_svg_confirm"))){
853 | try{
854 | var active_layer = paper.project.activeLayer;
855 | var layer = body2path();
856 | active_layer.visible = false;
857 |
858 | if(_spec.url) URL.revokeObjectURL(_spec.url);
859 | var svg = paper.project.exportSVG();
860 | var seri = new XMLSerializer();
861 | var s = seri.serializeToString(svg);
862 | _spec.url = handleDownload(s);
863 | alert(getMessage("exported"));
864 | } catch(e){
865 | console.log(e);
866 | alert(getMessage("failed_to_export"));
867 | } finally {
868 | active_layer.activate();
869 | active_layer.visible = true;
870 | layer.remove();
871 | }
872 | }
873 | }
874 |
875 | function body2path(){
876 | var all_bodies = Composite.allBodies(_engine.world);
877 | var layer = new paper.Layer();
878 | layer.activate();
879 | for(var i = 0, iEnd = all_bodies.length; i < iEnd ; i++){
880 | var body = all_bodies[i];
881 | if(!(body.id in _svgShapes)) continue;
882 |
883 | // クライアント枠の外側にあるものは出力しない
884 | if(body.bounds.max.y < 0 || body.bounds.min.y > window.innerHeight
885 | || body.bounds.max.x < 0 || body.bounds.min.x > window.innerWidth){
886 | continue;
887 | }
888 |
889 | var path = _svgShapes[body.id].copyTo(layer);
890 | if(body.label != "Circle Body"){
891 | path.rotate(body.angle * 180 / Math.PI);
892 | }
893 | var body_center = new paper.Point(
894 | (body.bounds.max.x + body.bounds.min.x) / 2,
895 | (body.bounds.max.y + body.bounds.min.y) / 2
896 | );
897 | path.translate(body_center.subtract(path.position));
898 | }
899 |
900 | // export hang constraints
901 | var all_constraints = Composite.allConstraints(_engine.world);
902 | for(var i = 0, iEnd = all_constraints.length; i < iEnd ; i++){
903 | var constraint = all_constraints[i];
904 | if(constraint.label == "hang"){
905 | var path = new paper.Path.Line(
906 | Constraint.pointAWorld(constraint),
907 | Constraint.pointBWorld(constraint));
908 | path.strokeColor = constraint.render.strokeStyle;
909 | path.strokeWidth = constraint.render.strokeWidth;
910 | }
911 | }
912 | return layer;
913 | }
914 |
915 | function handleDownload(text) {
916 | var blob = new Blob([ text ], { "type" : "image/svg+xml" });
917 | var a = document.createElement("a");
918 | a.target = '_blank';
919 | a.download = EXPORT_SVG_FILENAME;
920 | var url;
921 | var winURL = window.URL || window.webkitURL;
922 | if(winURL && winURL.createObject) { //chrome
923 | url = winURL.createObjectURL(blob);
924 | a.href = url;
925 | a.click();
926 | } else if(window.URL && window.URL.createObjectURL) { //firefox
927 | url = window.URL.createObjectURL(blob);
928 | a.href = url;
929 | document.body.appendChild(a);
930 | a.click();
931 | document.body.removeChild(a);
932 | }
933 | return url;
934 | }
935 |
936 | // ----------------------
937 | // initialize
938 | // ----------------------
939 | // HTML上のステータステキストを更新する
940 | function updateStatus(){
941 | var lang = getLang();
942 |
943 | $("#stat_gravity_" + lang).text(
944 | getMessage("stat_gravity") + (_spec.gravity_y ? GRAVITY_Y_DEFAULT : 0));
945 | $("#stat_gravity_" + lang).css("color", _spec.gravity_y ? "#888" : "red");
946 |
947 | $("#stat_frictionAir_" + lang).text(
948 | getMessage("stat_frictionAir")
949 | + (_spec.frictionAir == FRICTIONAIR_DEFAULT
950 | ? getMessage("stat_normal")
951 | : getMessage("stat_frictionAir_high")));
952 | $("#stat_frictionAir_" + lang).css(
953 | "color",
954 | _spec.frictionAir == FRICTIONAIR_DEFAULT ? "#888" : "red");
955 |
956 | $("#stat_display_" + lang).text(
957 | getMessage("stat_display")
958 | + (_spec.wireframes
959 | ? getMessage("stat_wireframes")
960 | : getMessage("stat_normal")));
961 | }
962 |
963 | function beforeApplyKeyCommand(){
964 | // キーイベントによるコマンド実行前の処理。
965 | // 動いている場合はポーズする
966 | var isRunning = !_spec.isPause;
967 | if(_runner && !_spec.isPause){
968 | Runner.stop(_runner);
969 | _spec.isPause = true;
970 | }
971 | return isRunning;
972 | }
973 |
974 | function afterApplyKeyCommand(isRunning){
975 | // キーイベントによるコマンド実行後の処理。
976 | // 実行前に動いていた場合は、また動かす
977 | if(isRunning && _runner && _spec.isPause){
978 | Runner.run(_runner, _engine);
979 | _spec.isPause = false;
980 | }
981 | }
982 |
983 | function key_exportSVGtoFile(){
984 | if(!_engine){
985 | alert(getMessage("no_data"));
986 | return;
987 | }
988 | var isRunning = beforeApplyKeyCommand();
989 | exportSVGtoFile();
990 | afterApplyKeyCommand(isRunning);
991 | }
992 |
993 | function key_wireFrame(){
994 | // 読み込み前に設定できるようにしている
995 | _spec.wireframes = !_spec.wireframes
996 | if(_render){
997 | _render.options.wireframes = _spec.wireframes;
998 | }
999 | updateStatus();
1000 | }
1001 |
1002 | function key_pause(){
1003 | if(_runner){
1004 | if(_spec.isPause){
1005 | Runner.run(_runner, _engine);
1006 | } else {
1007 | Runner.stop(_runner);
1008 | }
1009 | _spec.isPause = !_spec.isPause;
1010 | }
1011 | }
1012 |
1013 | function key_gravity(){
1014 | // 読み込み前に設定できるようにしている
1015 | _spec.gravity_y = _spec.gravity_y ? 0 : GRAVITY_Y_DEFAULT;
1016 | if(_engine){
1017 | _engine.world.gravity.y = _spec.gravity_y;
1018 | }
1019 | updateStatus();
1020 | }
1021 |
1022 | function key_frictionAir(){
1023 | // 読み込み前に設定できるようにしている
1024 | _spec.frictionAir = _spec.frictionAir == FRICTIONAIR_DEFAULT ? FRICTIONAIR_HIGH : FRICTIONAIR_DEFAULT;
1025 | if(_engine){
1026 | var isRunning = beforeApplyKeyCommand();
1027 |
1028 | var bodies = Composite.allBodies(_engine.world);
1029 | for(var i = 0; i < bodies.length; i++){
1030 | var body = bodies[i];
1031 | if( body.isStatic) continue;
1032 | body.frictionAir = _spec.frictionAir;
1033 | }
1034 |
1035 | afterApplyKeyCommand(isRunning);
1036 | }
1037 | updateStatus();
1038 | }
1039 |
1040 | function init() {
1041 | showHelpByLocale();
1042 |
1043 | document.addEventListener('dragover', handleDragOver);
1044 | document.addEventListener('drop', handleFileSelect);
1045 |
1046 | document.addEventListener('keydown',(evt) => {
1047 | const keyName = evt.key.toLowerCase();
1048 | if(keyName == "s"){
1049 | key_exportSVGtoFile();
1050 | } else if(keyName == "v"){
1051 | importSVGFromClipboard_paper();
1052 | } else if(keyName == "w"){
1053 | key_wireFrame();
1054 | } else if(keyName == "p"){
1055 | key_pause();
1056 | } else if(keyName == "g"){
1057 | key_gravity();
1058 | } else if(keyName == "a"){
1059 | key_frictionAir();
1060 | }
1061 | });
1062 | }
1063 |
1064 | function showHelpByLocale(){
1065 | if(getLang() == "ja"){
1066 | $("#help_ja").show();
1067 | $("#status_ja").show();
1068 | } else {
1069 | $("#help_en").show();
1070 | $("#status_en").show();
1071 | }
1072 | }
1073 |
1074 | // ----------------------
1075 | // locale
1076 | // ----------------------
1077 | function getLang(){
1078 | //return "en"; // for test
1079 | return navigator.language.startsWith("ja") ? "ja" : "en";
1080 | }
1081 |
1082 | function getMessage(entry){
1083 | var obj = _messages[entry];
1084 | var lang = getLang();
1085 |
1086 | if(obj){
1087 | return obj[lang] || obj["en"].toString();
1088 | } else {
1089 | return "undefined:[" + entry + "]";
1090 | }
1091 | }
1092 |
1093 | _messages = {
1094 | already_loaded : {
1095 | "en" : "already loaded. please reload the page to refresh",
1096 | "ja" : "すでに読み込まれたデータがあります。別のデータを読み込むには頁をリロードしてください。"
1097 | },
1098 | please_select_a_svg : {
1099 | "en" : "Please drop a SVG file.(Dropped file is ",
1100 | "ja" : "SVGファイルをドロップしてください。(入力ファイル="
1101 | },
1102 | paste_failed : {
1103 | "en" : "paste failed",
1104 | "ja" : "貼付データの読み込みに失敗しました"
1105 | },
1106 | paste_chrome_only_for_now : {
1107 | "en" : "Loading by paste is currently available only for Chrome(ver.66 or later)",
1108 | "ja" : "貼付によるロードは今のところChrome(ver.66以降)のみ対応です"
1109 | },
1110 | no_data : {
1111 | "en" : "no data",
1112 | "ja" : "出力データなし"
1113 | },
1114 | export_svg_confirm : {
1115 | "en" : "export SVG file?",
1116 | "ja" : "SVGファイルをエクスポートしますか?"
1117 | },
1118 | exported : {
1119 | "en" : "exported",
1120 | "ja" : "エクスポート完了"
1121 | },
1122 | failed_to_export : {
1123 | "en" : "failed to export",
1124 | "ja" : "エクスポートに失敗しました"
1125 | },
1126 | stat_gravity : {
1127 | "en" : "gravity=",
1128 | "ja" : "重力="
1129 | },
1130 | stat_frictionAir : {
1131 | "en" : "airResistance=",
1132 | "ja" : "空気抵抗="
1133 | },
1134 | stat_normal : {
1135 | "en" : "normal",
1136 | "ja" : "通常"
1137 | },
1138 | stat_frictionAir_high : {
1139 | "en" : "HIGH",
1140 | "ja" : "重い"
1141 | },
1142 | stat_display : {
1143 | "en" : "display=",
1144 | "ja" : "表示="
1145 | },
1146 | stat_wireframes : {
1147 | "en" : "wireframes",
1148 | "ja" : "ワイヤーフレーム"
1149 | }
1150 | }
1151 |
1152 | init();
1153 | }());
1154 |
1155 |
--------------------------------------------------------------------------------
/js/lib/jquery-3.7.1.slim.min.js:
--------------------------------------------------------------------------------
1 | /*! jQuery v3.7.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/animatedSelector,-effects/Tween | (c) OpenJS Foundation and other contributors | jquery.org/license */
2 | !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(ie,e){"use strict";var oe=[],r=Object.getPrototypeOf,ae=oe.slice,g=oe.flat?function(e){return oe.flat.call(e)}:function(e){return oe.concat.apply([],e)},s=oe.push,se=oe.indexOf,n={},i=n.toString,ue=n.hasOwnProperty,o=ue.toString,a=o.call(Object),le={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},y=function(e){return null!=e&&e===e.window},m=ie.document,u={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||m).createElement("script");if(o.text=e,t)for(r in u)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e}var t="3.7.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/animatedSelector,-effects/Tween",l=/HTML$/i,ce=function(e,t){return new ce.fn.init(e,t)};function c(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+ge+")"+ge+"*"),b=new RegExp(ge+"|>"),A=new RegExp(g),D=new RegExp("^"+t+"$"),N={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+d),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},L=/^(?:input|select|textarea|button)$/i,j=/^h\d$/i,O=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,P=/[+~]/,H=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),q=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},R=function(){V()},M=K(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{E.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){E={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,d=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==d&&9!==d&&11!==d)return n;if(!r&&(V(e),e=e||C,T)){if(11!==d&&(u=O.exec(t)))if(i=u[1]){if(9===d){if(!(a=e.getElementById(i)))return n;if(a.id===i)return E.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return E.call(n,a),n}else{if(u[2])return E.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return E.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||p&&p.test(t))){if(c=t,f=e,1===d&&(b.test(t)||m.test(t))){(f=P.test(t)&&X(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=k)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+G(l[o]);c=l.join(",")}try{return E.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===k&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>x.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function B(e){return e[k]=!0,e}function F(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function $(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function z(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&M(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function U(a){return B(function(o){return o=+o,B(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function X(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=C&&9===n.nodeType&&n.documentElement&&(r=(C=n).documentElement,T=!ce.isXMLDoc(C),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,r.msMatchesSelector&&ye!=C&&(t=C.defaultView)&&t.top!==t&&t.addEventListener("unload",R),le.getById=F(function(e){return r.appendChild(e).id=ce.expando,!C.getElementsByName||!C.getElementsByName(ce.expando).length}),le.disconnectedMatch=F(function(e){return i.call(e,"*")}),le.scope=F(function(){return C.querySelectorAll(":scope")}),le.cssHas=F(function(){try{return C.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(x.filter.ID=function(e){var t=e.replace(H,q);return function(e){return e.getAttribute("id")===t}},x.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&T){var n=t.getElementById(e);return n?[n]:[]}}):(x.filter.ID=function(e){var n=e.replace(H,q);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},x.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&T){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),x.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},x.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&T)return t.getElementsByClassName(e)},p=[],F(function(e){var t;r.appendChild(e).innerHTML=" ",e.querySelectorAll("[selected]").length||p.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+k+"-]").length||p.push("~="),e.querySelectorAll("a#"+k+"+*").length||p.push(".#.+[+~]"),e.querySelectorAll(":checked").length||p.push(":checked"),(t=C.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&p.push(":enabled",":disabled"),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||p.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||p.push(":has"),p=p.length&&new RegExp(p.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===C||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===C||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),C}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),T&&!h[t+" "]&&(!p||!p.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(H,q),e[3]=(e[3]||e[4]||e[5]||"").replace(H,q),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return N.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&A.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(H,q).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||E,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:k.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:m,!0)),C.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=m.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,E=ce(m);var S=/^(?:parents|prev(?:Until|All))/,A={children:!0,contents:!0,next:!0,prev:!0};function D(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;re=m.createDocumentFragment().appendChild(m.createElement("div")),(be=m.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),re.appendChild(be),le.checkClone=re.cloneNode(!0).cloneNode(!0).lastChild.checked,re.innerHTML="",le.noCloneChecked=!!re.cloneNode(!0).lastChild.defaultValue,re.innerHTML=" ",le.option=!!re.lastChild;var Te={thead:[1,""],col:[2,""],tr:[2,""],td:[3,""],_default:[0,"",""]};function Ee(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function ke(e,t){for(var n=0,r=e.length;n",""]);var Se=/<|?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),d=[],p=0,h=e.length;p\s*$/g;function Re(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Me(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Ie(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function We(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n",2===yt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=m.implementation.createHTMLDocument("")).createElement("base")).href=m.location.href,t.head.appendChild(r)):t=m),o=!n&&[],(i=C.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||K})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return R(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Qe(le.pixelPosition,function(e,t){if(t)return t=Ve(e,n),$e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return R(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 0