1 | const NUM_POSITIONS_CHUNK = 150 * 1024;
|
2 | const ROTATE_SPEED = 0.0004;
|
3 | const ASSET_SHADER = {
|
4 | uniforms: {
|
5 | theta: {
|
6 | type: 'f',
|
7 | value: 0,
|
8 | },
|
9 | },
|
10 | vertexShader: [
|
11 | "uniform float theta;",
|
12 | "attribute vec3 color;",
|
13 | "attribute vec2 dy;",
|
14 | "varying vec3 vcolor;",
|
15 |
|
16 | "void main() {",
|
17 | " gl_Position = projectionMatrix * modelViewMatrix * vec4(position.x - dy.x + (dy.x*cos(theta) - dy.y*sin(theta)), position.y, position.z - dy.y + (dy.y*cos(theta) + dy.x*sin(theta)), 1.0);",
|
18 | " vcolor = color;",
|
19 | "}"
|
20 | ].join("\n"),
|
21 | fragmentShader: [
|
22 | "varying vec3 vcolor;",
|
23 | "void main() {",
|
24 | " gl_FragColor = vec4(vcolor, 1.0);",
|
25 | "}"
|
26 | ].join("\n")
|
27 | };
|
28 | const assetMaterial = new THREE.ShaderMaterial({
|
29 | uniforms: THREE.UniformsUtils.clone(ASSET_SHADER.uniforms),
|
30 | vertexShader: ASSET_SHADER.vertexShader,
|
31 | fragmentShader: ASSET_SHADER.fragmentShader,
|
32 | });
|
33 | const _makeImageDataGeometry = (width, height, size, imageDataData) => {
|
34 | const halfSize = size / 2;
|
35 | const vertices = [
|
36 | [-halfSize, halfSize, -halfSize],
|
37 | [halfSize, halfSize, -halfSize],
|
38 | [-halfSize, halfSize, halfSize],
|
39 | [halfSize, halfSize, halfSize],
|
40 | [-halfSize, -halfSize, -halfSize],
|
41 | [halfSize, -halfSize, -halfSize],
|
42 | [-halfSize, -halfSize, halfSize],
|
43 | [halfSize, -halfSize, halfSize],
|
44 | ];
|
45 | const getPixelValue = (imageDataData, x, y, pixelData) => {
|
46 | const index = (x + y * width) * 4;
|
47 | pixelData[0] = imageDataData[index + 0];
|
48 | pixelData[1] = imageDataData[index + 1];
|
49 | pixelData[2] = imageDataData[index + 2];
|
50 | pixelData[3] = imageDataData[index + 3];
|
51 | };
|
52 | const getPixelVertices = (x, y, left, right, top, bottom) => {
|
53 | const result = vertices[2].concat(vertices[6]).concat(vertices[3])
|
54 | .concat(vertices[6]).concat(vertices[7]).concat(vertices[3])
|
55 | .concat(vertices[1]).concat(vertices[5]).concat(vertices[0])
|
56 | .concat(vertices[5]).concat(vertices[4]).concat(vertices[0]);
|
57 |
|
58 | if (left) {
|
59 | result.push.apply(
|
60 | result,
|
61 | vertices[0].concat(vertices[4]).concat(vertices[2])
|
62 | .concat(vertices[4]).concat(vertices[6]).concat(vertices[2])
|
63 | );
|
64 | }
|
65 | if (right) {
|
66 | result.push.apply(
|
67 | result,
|
68 | vertices[3].concat(vertices[7]).concat(vertices[1])
|
69 | .concat(vertices[7]).concat(vertices[5]).concat(vertices[1])
|
70 | );
|
71 | }
|
72 | if (top) {
|
73 | result.push.apply(
|
74 | result,
|
75 | vertices[0].concat(vertices[2]).concat(vertices[1])
|
76 | .concat(vertices[2]).concat(vertices[3]).concat(vertices[1])
|
77 | );
|
78 | }
|
79 | if (bottom) {
|
80 | result.push.apply(
|
81 | result,
|
82 | vertices[6].concat(vertices[4]).concat(vertices[7])
|
83 | .concat(vertices[4]).concat(vertices[5]).concat(vertices[7])
|
84 | );
|
85 | }
|
86 |
|
87 | const numPositions = result.length / 3;
|
88 | const xOffset = (-(width / 2) + x) * size;
|
89 | const yOffset = ((height / 2) - y) * size;
|
90 | for (let i = 0; i < numPositions; i++) {
|
91 | const baseIndex = i * 3;
|
92 | result[baseIndex + 0] += xOffset;
|
93 | result[baseIndex + 1] += yOffset;
|
94 | result[baseIndex + 2] += size / 2;
|
95 | }
|
96 | return Float32Array.from(result);
|
97 | };
|
98 | const isSolidPixel = (x, y) => imageDataData[((x + y * width) * 4) + 3] >= 128;
|
99 |
|
100 | const positions = new Float32Array(NUM_POSITIONS_CHUNK);
|
101 | const colors = new Float32Array(NUM_POSITIONS_CHUNK);
|
102 | let attributeIndex = 0;
|
103 | const pixelData = Array(4);
|
104 | for (let y = 0; y < height; y++) {
|
105 | for (let x = 0; x < width; x++) {
|
106 | getPixelValue(imageDataData, x, y, pixelData);
|
107 |
|
108 | if (pixelData[3] >= 128) {
|
109 | const newPositions = getPixelVertices(
|
110 | x,
|
111 | y,
|
112 | !((x - 1) >= 0 && isSolidPixel(x - 1, y)),
|
113 | !((x + 1) < width && isSolidPixel(x + 1, y)),
|
114 | !((y - 1) >= 0 && isSolidPixel(x, y - 1)),
|
115 | !((y + 1) < height && isSolidPixel(x, y + 1))
|
116 | );
|
117 | positions.set(newPositions, attributeIndex);
|
118 |
|
119 | const numNewPositions = newPositions.length / 3;
|
120 | const rFactor = pixelData[0] / 255;
|
121 | const gFactor = pixelData[1] / 255;
|
122 | const bFactor = pixelData[2] / 255;
|
123 | for (let i = 0; i < numNewPositions; i++) {
|
124 | const baseIndex = i * 3;
|
125 | colors[attributeIndex + baseIndex + 0] = rFactor;
|
126 | colors[attributeIndex + baseIndex + 1] = gFactor;
|
127 | colors[attributeIndex + baseIndex + 2] = bFactor;
|
128 | }
|
129 |
|
130 | attributeIndex += newPositions.length;
|
131 | }
|
132 | }
|
133 | }
|
134 |
|
135 | const geometry = new THREE.BufferGeometry();
|
136 | geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(positions.buffer, 0, attributeIndex), 3));
|
137 | geometry.addAttribute('color', new THREE.BufferAttribute(new Float32Array(colors.buffer, 0, attributeIndex), 3));
|
138 |
|
139 | const numPositions = attributeIndex / 3;
|
140 | const dys = new Float32Array(numPositions * 2);
|
141 | for (let i = 0; i < numPositions; i++) {
|
142 | dys[(i * 2) + 0] = positions[(i * 3) + 0];
|
143 | dys[(i * 2) + 1] = positions[(i * 3) + 2];
|
144 | }
|
145 |
|
146 | geometry.addAttribute('dy', new THREE.BufferAttribute(dys, 2));
|
147 |
|
148 | return geometry;
|
149 | };
|
150 |
|
151 | const renderer = new THREE.WebGLRenderer({
|
152 | alpha: true,
|
153 | });
|
154 | renderer.setSize(100, 100);
|
155 | renderer.setPixelRatio(window.devicePixelRatio);
|
156 | renderer.setClearColor(0x000000, 0);
|
157 | const scene = new THREE.Scene();
|
158 | scene.matrixAutoUpdate = false;
|
159 | const camera = new THREE.PerspectiveCamera(45, 100 / 100, 0.1, 100);
|
160 | camera.position.set(0, 0, 15);
|
161 | camera.lookAt(new THREE.Vector3(0, 0, 0));
|
162 | camera.updateMatrixWorld();
|
163 | camera.updateProjectionMatrix();
|
164 |
|
165 | const updates = [];
|
166 | const _update = () => {
|
167 | assetMaterial.uniforms.theta.value = (Date.now() * ROTATE_SPEED * (Math.PI * 2) % (Math.PI * 2));
|
168 | for (let i = 0; i < updates.length; i++) {
|
169 | updates[i]();
|
170 | }
|
171 | requestAnimationFrame(_update);
|
172 | };
|
173 | _update();
|
174 |
|
175 | class SpriteRenderer {
|
176 | constructor(img, canvas) {
|
177 | this.img = img;
|
178 |
|
179 | this.imageData = (() => {
|
180 | const canvas = document.createElement('canvas');
|
181 | canvas.width = img.width;
|
182 | canvas.height = img.height;
|
183 | const ctx = canvas.getContext('2d');
|
184 | ctx.drawImage(img, 0, 0);
|
185 | return ctx.getImageData(0, 0, canvas.width, canvas.height);
|
186 | })();
|
187 | this.cleanups = [];
|
188 |
|
189 | this.addCanvas(canvas);
|
190 | }
|
191 |
|
192 | addCanvas(canvas) {
|
193 | const ctx = canvas.getContext('2d');
|
194 | const geometry = _makeImageDataGeometry(this.imageData.width, this.imageData.height, 0.5, this.imageData.data);
|
195 | const material = assetMaterial;
|
196 | const mesh = new THREE.Mesh(geometry, material);
|
197 | const update = () => {
|
198 | scene.add(mesh);
|
199 | renderer.render(scene, camera);
|
200 | scene.remove(mesh);
|
201 |
|
202 | ctx.clearRect(0, 0, canvas.width, canvas.height);
|
203 | ctx.drawImage(renderer.domElement, 0, 0, canvas.width, canvas.height);
|
204 | };
|
205 | updates.push(update);
|
206 |
|
207 | const cleanup = () => {
|
208 | ctx.clearRect(0, 0, canvas.width, canvas.height);
|
209 |
|
210 | geometry.dispose();
|
211 | updates.splice(updates.indexOf(update), 1);
|
212 | };
|
213 | cleanup.canvas = canvas;
|
214 | this.cleanups.push(cleanup);
|
215 | }
|
216 |
|
217 | removeCanvas(canvas) {
|
218 | const cleanupIndex = this.cleanups.findIndex(cleanup => cleanup.canvas === canvas);
|
219 | if (cleanupIndex !== -1) {
|
220 | this.cleanups[cleanupIndex]();
|
221 | this.cleanups.splice(cleanupIndex, 1);
|
222 | }
|
223 | }
|
224 |
|
225 | destroy() {
|
226 | for (let i = 0; i < this.cleanups.length; i++) {
|
227 | this.cleanups[i]();
|
228 | }
|
229 | this.cleanups.length = 0;
|
230 | }
|
231 | }
|