├── 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 | ![ss](https://github.com/yaegaki/emoji-physics/blob/master/emoji.png?raw=true) 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 | })(); --------------------------------------------------------------------------------