UNPKG

7.6 kBJavaScriptView Raw
1const NUM_POSITIONS_CHUNK = 150 * 1024;
2const ROTATE_SPEED = 0.0004;
3const 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 // `float rotateSpeed = ${ROTATE_SPEED.toFixed(8)};`,
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};
28const assetMaterial = new THREE.ShaderMaterial({
29 uniforms: THREE.UniformsUtils.clone(ASSET_SHADER.uniforms),
30 vertexShader: ASSET_SHADER.vertexShader,
31 fragmentShader: ASSET_SHADER.fragmentShader,
32});
33const _makeImageDataGeometry = (width, height, size, imageDataData) => {
34 const halfSize = size / 2;
35 const vertices = [
36 [-halfSize, halfSize, -halfSize], // 0 left up back
37 [halfSize, halfSize, -halfSize], // 1 right up back
38 [-halfSize, halfSize, halfSize], // 2 left up front
39 [halfSize, halfSize, halfSize], // 3 right up front
40 [-halfSize, -halfSize, -halfSize], // 4 left down back
41 [halfSize, -halfSize, -halfSize], // 5 right down back
42 [-halfSize, -halfSize, halfSize], // 6 left down front
43 [halfSize, -halfSize, halfSize], // 7 right down front
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]) // front
54 .concat(vertices[6]).concat(vertices[7]).concat(vertices[3])
55 .concat(vertices[1]).concat(vertices[5]).concat(vertices[0]) // back
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
151const renderer = new THREE.WebGLRenderer({
152 alpha: true,
153});
154renderer.setSize(100, 100);
155renderer.setPixelRatio(window.devicePixelRatio);
156renderer.setClearColor(0x000000, 0);
157const scene = new THREE.Scene();
158scene.matrixAutoUpdate = false;
159const camera = new THREE.PerspectiveCamera(45, 100 / 100, 0.1, 100);
160camera.position.set(0, 0, 15);
161camera.lookAt(new THREE.Vector3(0, 0, 0));
162camera.updateMatrixWorld();
163camera.updateProjectionMatrix();
164
165const updates = [];
166const _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
175class 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}