├── emoji.png
├── README.md
├── index.html
└── main.js
/emoji.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yaegaki/emoji-physics/HEAD/emoji.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # emoji-physics
2 |
3 | 
4 |
5 | ## demo
6 |
7 | [demo page](https://yaegaki.github.io/emoji-physics/)
8 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
14 | Emoji Physics
15 |
16 |
17 |
18 |
Emoji List:
19 |
29 |
30 |
31 | Input:
32 |
33 |
34 |
35 | Font:
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
Github:
46 | yaegaki/emoji-physics
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | (function () {
2 |
3 | // create matter engine
4 | function createEngine(parentNode) {
5 | const Engine = Matter.Engine;
6 | const World = Matter.World;
7 | const Bodies = Matter.Bodies;
8 | const MouseConstraint = Matter.MouseConstraint;
9 |
10 | let engine = Engine.create(parentNode, {
11 | render: {
12 | options: {
13 | wireframes: false,
14 | background: 'white'
15 | }
16 | }
17 | });
18 |
19 | // Create ground and wall
20 | let ground = Bodies.rectangle(400, 610, 1000, 60, { isStatic: true });
21 | let leftWall = Bodies.rectangle(100, 0, 30, 1500, { isStatic: true });
22 | let rightWall = Bodies.rectangle(700, 0, 30, 1500, { isStatic: true });
23 | World.add(engine.world, [ground, leftWall, rightWall]);
24 |
25 | // Add Box
26 | let boxA = Bodies.rectangle(400, 200, 80, 80);
27 | let boxB = Bodies.rectangle(430, 30, 50, 50);
28 | World.add(engine.world, [boxA, boxB]);
29 |
30 | let mouseConstraint = MouseConstraint.create(engine);
31 | World.add(engine.world, mouseConstraint);
32 |
33 | Engine.run(engine);
34 | return engine;
35 | }
36 |
37 | // split emoji string
38 | function stringToArray(str) {
39 | return str.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[^\uD800-\uDFFF]/g) || [];
40 | }
41 |
42 | function createTexture(sourceCanvas, bounds) {
43 | let canvas = document.createElement('canvas');
44 | canvas.width = bounds.max.x - bounds.min.x + 1;
45 | canvas.height = bounds.max.y - bounds.min.y + 1;
46 |
47 | canvas.getContext('2d').drawImage(sourceCanvas, bounds.min.x, bounds.min.y, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height);
48 | return canvas.toDataURL();
49 | }
50 |
51 | function alphaToWhite(data8U) {
52 | for (let i = 0; i < data8U.length; i += 4) {
53 | if (data8U[i + 3] == 0) {
54 | data8U[i] = 255;
55 | data8U[i + 1] = 255;
56 | data8U[i + 2] = 255;
57 | data8U[i + 3] = 255;
58 | }
59 | }
60 | }
61 |
62 | function createEmojiInfo(emoji, font) {
63 | let canvas = document.createElement('canvas');
64 | canvas.width = 50;
65 | canvas.height = 50;
66 | let context = canvas.getContext('2d');
67 |
68 | // draw text
69 | context.fillStyle = 'black';
70 | if (font == '') {
71 | // force fallback font
72 | context.font = '30px EMOJI_PHYSICS';
73 | }
74 | else {
75 | context.font = '30px "' + font + '"';
76 | }
77 |
78 | context.fillText(emoji, 10, 40);
79 |
80 | const emojiImage = canvas.toDataURL();
81 | let source = cv.imread(canvas);
82 | alphaToWhite(source.data);
83 | let destC1 = new cv.Mat(canvas.height, canvas.width, cv.CV_8UC1);
84 | let destC4 = new cv.Mat(canvas.height, canvas.width, cv.CV_8UC4);
85 |
86 | cv.cvtColor(source, destC1, cv.COLOR_RGBA2GRAY);
87 | cv.threshold(destC1, destC4, 254, 255, cv.THRESH_BINARY);
88 | cv.bitwise_not(destC4, destC4);
89 |
90 | let contours = new cv.MatVector();
91 | let hierarchy = new cv.Mat();
92 | cv.findContours(destC4, contours, hierarchy, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE, { x: 0, y: 0});
93 | hierarchy.delete();
94 | destC1.delete();
95 | destC4.delete();
96 | source.delete();
97 |
98 | let points = [];
99 | for (let i = 0; i < contours.size(); i++) {
100 | let d = contours.get(i).data32S;
101 | for (let j = 0; j < d.length; j++) {
102 | points.push(d[j]);
103 | }
104 | }
105 | contours.delete();
106 |
107 | if (points.length < 3) {
108 | return null;
109 | }
110 |
111 | let _points = new cv.Mat(1, points.length / 2, cv.CV_32SC2);
112 | let d = _points.data32S;
113 | for (let i = 0; i < points.length; i++) {
114 | d[i] = points[i];
115 | }
116 | let hull = new cv.Mat();
117 | cv.convexHull(_points, hull);
118 | _points.delete();
119 |
120 | let vert = [];
121 | d = hull.data32S;
122 | for (let i = 0; i < d.length; i += 2) {
123 | vert.push({ x: d[i], y: d[i + 1]});
124 | }
125 | hull.delete();
126 |
127 | const bounds = Matter.Bounds.create(vert);
128 | const texture = createTexture(canvas, bounds);
129 |
130 | return {
131 | vert: vert,
132 | texture: texture
133 | };
134 | }
135 |
136 | let emojiCache = {};
137 | function addToWorld(engine, emoji, font, x) {
138 | if (!emojiCache.hasOwnProperty(font)) {
139 | emojiCache[font] = {};
140 | }
141 |
142 | let emojiInfoCache = emojiCache[font];
143 | if (!emojiInfoCache.hasOwnProperty(emoji)) {
144 | emojiInfoCache[emoji] = createEmojiInfo(emoji, font);
145 | }
146 |
147 | const info = emojiInfoCache[emoji];
148 | if (info == null) {
149 | console.warn('Can not add "' + emoji + '" to world');
150 | return;
151 | }
152 |
153 | let emojiBody = Matter.Bodies.fromVertices(x, 0, info.vert, {
154 | render: {
155 | sprite: {
156 | texture: info.texture
157 | }
158 | }
159 | });
160 |
161 | Matter.World.add(engine.world, emojiBody);
162 | }
163 |
164 | function getEmojiArray(str) {
165 | const array = stringToArray(str)
166 | .map(s => s.replace(/\s/g, ''))
167 | .filter(s => s.length > 0);
168 |
169 | return array;
170 | }
171 |
172 | let emojiListTextarea = document.getElementById('emoji-list');
173 | let engine = createEngine(document.getElementById('world'));
174 | const initialBodiesLength = engine.world.bodies.length;
175 |
176 | let input = document.getElementById('input');
177 | let fontInput = document.getElementById('font-input');
178 | document.getElementById('add-button').addEventListener('click', () => {
179 | let array = getEmojiArray(input.value);
180 | let x = 400 - (array.length - 1) / 2 * 30;
181 | array.forEach(s => {
182 | addToWorld(engine, s, fontInput.value, x);
183 | x += 30;
184 | });
185 | });
186 |
187 | document.getElementById('random-button').addEventListener('click', () => {
188 | const array = getEmojiArray(emojiListTextarea.value);
189 | if (array.length == 0) {
190 | return;
191 | }
192 |
193 | const emoji = array[Math.floor(Math.random() * array.length)];
194 | addToWorld(engine, emoji, fontInput.value, 400);
195 | });
196 |
197 | document.getElementById('clear-button').addEventListener('click', () => {
198 | const count = engine.world.bodies.length - initialBodiesLength;
199 | for (let i = 0; i < count; i++) {
200 | let body = engine.world.bodies[initialBodiesLength];
201 | Matter.Composite.removeBody(engine.world, body);
202 | }
203 | });
204 | })();
--------------------------------------------------------------------------------