forked from holopot360/holopot360.github.io
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
382 lines (327 loc) · 12.1 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Canvas Half-Width, Full-Height + No-Cropping</title>
<style>
html, body {
margin: 0;
padding: 0;
height: 100%;
background: black;
overflow: hidden;
}
#glCanvas {
display: block;
position: absolute;
left: 50%;
transform: translateX(-50%);
}
</style>
</head>
<body>
<canvas id="glCanvas"></canvas>
<script>
(function() {
// ---------------------------------------------------------
// 1) Parse URL params: mode, frequency, rps
// ---------------------------------------------------------
const params = new URLSearchParams(window.location.search);
const mode = params.get('mode') || 'test'; // e.g. "test", "viking4", "viking32", "statue32"
const freq = parseFloat(params.get('frequency') || '120');
const rps = parseFloat(params.get('rps') || '3.75');
const numViews = parseFloat(params.get('numViews') || '32');
const skipSprite = 32 / numViews;
// framesToSwitch => how many frames pass before toggling/moving
let framesToSwitch;
if (mode === 'test') {
// same as before
framesToSwitch = (freq / rps) * 0.5;
} else if (mode === 'viking32' || mode === 'statue32') {
// use the new formula: (freq / rps) * (1 / numViews)
framesToSwitch = (freq / rps) * (1 / numViews);
} else {
// default for other texture modes (e.g. "viking4")
framesToSwitch = (freq / rps) * 0.25;
}
framesToSwitch = Math.round(framesToSwitch);
if (framesToSwitch < 1) framesToSwitch = 1;
// ---------------------------------------------------------
// 2) Canvas and WebGL setup
// ---------------------------------------------------------
const canvas = document.getElementById('glCanvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (!gl) {
alert('WebGL not supported');
return;
}
function resizeCanvas() {
const h = window.innerHeight;
const w = h / 2; // half the height => tall vertical layout
canvas.style.height = h + 'px';
canvas.style.width = w + 'px';
canvas.width = w;
canvas.height = h;
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// Toggle fullscreen on a click
canvas.addEventListener('click', () => {
if (!document.fullscreenElement) {
canvas.requestFullscreen();
} else {
document.exitFullscreen();
}
});
// ---------------------------------------------------------
// 3) Shaders
// ---------------------------------------------------------
const vsSource = `
attribute vec2 aPosition;
attribute vec2 aTexCoord;
attribute float aColorMode;
varying vec2 vTexCoord;
varying float vColorMode;
void main(void) {
vTexCoord = aTexCoord;
vColorMode = aColorMode;
gl_Position = vec4(aPosition, 0.0, 1.0);
}
`;
const fsSource = `
precision mediump float;
varying vec2 vTexCoord;
varying float vColorMode;
uniform vec4 uSolidColor;
uniform sampler2D uSampler;
void main(void) {
// If vColorMode > 0.5 => "test" mode => solid color
// else => "texture" mode => sample from sprite
if (vColorMode > 0.5) {
gl_FragColor = uSolidColor;
} else {
gl_FragColor = texture2D(uSampler, vTexCoord);
}
}
`;
function loadShader(type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error('Error compiling shader:', gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
const vertexShader = loadShader(gl.VERTEX_SHADER, vsSource);
const fragmentShader = loadShader(gl.FRAGMENT_SHADER, fsSource);
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
console.error('Could not link shader program:', gl.getProgramInfoLog(shaderProgram));
return;
}
gl.useProgram(shaderProgram);
// Location lookups
const aPosition = gl.getAttribLocation(shaderProgram, 'aPosition');
const aTexCoord = gl.getAttribLocation(shaderProgram, 'aTexCoord');
const aColorMode = gl.getAttribLocation(shaderProgram, 'aColorMode');
const uSolidColor = gl.getUniformLocation(shaderProgram, 'uSolidColor');
const uSampler = gl.getUniformLocation(shaderProgram, 'uSampler');
// ---------------------------------------------------------
// 4) Buffers (interleaved): [pos.x, pos.y, tex.x, tex.y, colorMode]
// ---------------------------------------------------------
const buffer = gl.createBuffer();
function createBufferData(posArray, texArray, colModeArray) {
// Each vertex => 5 floats: pos(2), tex(2), colorMode(1)
const data = [];
for (let i = 0; i < posArray.length; i += 2) {
data.push(
posArray[i], posArray[i + 1], // position
texArray[i], texArray[i + 1], // texCoord
colModeArray[i / 2] // colorMode
);
}
return new Float32Array(data);
}
let positions = null;
let texcoords = null;
let colorModeArr = null;
function setupQuad(aspect) {
// Fit the sub-frame fully in [-1..+1].
// If aspect >= 1 => wide => width=2, height=2/aspect
// If aspect < 1 => tall => height=2, width=2*aspect
let halfWidth, halfHeight;
if (aspect >= 1.0) {
halfWidth = 1.0;
halfHeight = 1.0 / aspect;
} else {
halfHeight = 1.0;
halfWidth = aspect;
}
positions = new Float32Array([
-halfWidth, -halfHeight,
halfWidth, -halfHeight,
-halfWidth, halfHeight,
halfWidth, halfHeight
]);
if (mode === 'test') {
// Fill entire texture space [0..1, 0..1], colorMode=1 => use solid color
texcoords = new Float32Array([0,0, 1,0, 0,1, 1,1]);
colorModeArr = [1, 1, 1, 1];
} else {
// Default "texture" coords will be updated each frame => colorMode=0
texcoords = new Float32Array([0,0, 1,0, 0,1, 1,1]);
colorModeArr = [0, 0, 0, 0];
}
const data = createBufferData(positions, texcoords, colorModeArr);
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
}
// ---------------------------------------------------------
// 5) Texture loading (if mode includes "texture")
// ---------------------------------------------------------
let texture = null;
let frameCount = 0;
let subImageIdx = 0;
const colorCyan = [0, 1, 1, 1];
const colorRed = [1, 0, 0, 1];
let currentColor = colorCyan;
// Example dictionary for the images
const textureImages = {
viking4: 'viking.png', // 4 sub-frames horizontally
viking32: 'viking32.png', // 32 sub-frames => 8×4
statue32: 'statue32.png' // 32 sub-frames => 8×4
};
if (mode in textureImages) {
// Flip Y to match typical top-left origin of images
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
texture = gl.createTexture();
const image = new Image();
image.src = textureImages[mode];
image.onload = function() {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,
gl.UNSIGNED_BYTE, image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
// Decide how many frames horizontally & vertically
let columns = 1;
let rows = 1;
if (mode === 'viking4') {
// 4 frames horizontally (1 row)
columns = 4;
rows = 1;
} else if (mode === 'viking32' || mode === 'statue32') {
// 32 frames => 8×4
columns = 8;
rows = 4;
}
// Each sub-frame is (1/columns) wide, (1/rows) tall.
// So sub-frame aspect = (image.width/columns) / (image.height/rows)
// = (image.width / image.height) * (rows / columns)
const subAspect = (image.width / columns) / (image.height / rows);
setupQuad(1);
requestAnimationFrame(() => render(columns, rows));
};
} else {
// mode="test"
setupQuad(1.0); // 1:1 aspect for a square
requestAnimationFrame(() => render(1, 1));
}
// ---------------------------------------------------------
// 6) Render loop
// ---------------------------------------------------------
function render(columns, rows) {
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
if (mode === 'test') {
// Toggle color every framesToSwitch
if (frameCount % framesToSwitch === 0) {
currentColor = (currentColor === colorCyan) ? colorRed : colorCyan;
}
gl.uniform4fv(uSolidColor, currentColor);
}
else {
// For multi-frame textures
// e.g. viking4 => 4 frames horizontally
// viking32 or statue32 => 32 frames in an 8×4 grid
const totalFrames = columns * rows;
// Switch to next frame every framesToSwitch
if (frameCount % framesToSwitch === 0) {
if (mode === 'viking32' || mode === 'statue32') {
// Decrement in reverse, wrapping around:
subImageIdx = (subImageIdx - skipSprite + totalFrames) % totalFrames;
} else {
// All other texture modes keep the normal forward cycle
subImageIdx = (subImageIdx + skipSprite) % totalFrames;
}
}
// Bind the texture & set sampler uniform
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.uniform1i(uSampler, 0);
// Figure out which row/column:
// We want to go left to right, then next row, top to bottom
// subImageIdx=0 => top-left
// subImageIdx=1 => top, next col
// ...
// subImageIdx=7 => top-right (if columns=8)
// subImageIdx=8 => second row, left
// ...
const col = subImageIdx % columns;
const row = Math.floor(subImageIdx / columns);
// If you want top row = row=0 in reading order, but the texture
// is flipped in Y, you can invert the row to ensure
// top is shown first:
const invertedRow = (rows - 1) - row;
// Compute the sub-frame in UV space
const uLeft = col * (1 / columns);
const uRight = uLeft + (1 / columns);
const vTop = invertedRow * (1 / rows);
const vBottom = vTop + (1 / rows);
// Rebuild the interleaved data => positions remain same
const data = [];
for (let i = 0; i < 4; i++) {
const px = positions[i * 2];
const py = positions[i * 2 + 1];
// original uv => i=0 => (0,0), i=1 => (1,0), i=2 => (0,1), i=3 => (1,1)
// Map oldU=0 => uLeft, oldU=1 => uRight
// oldV=0 => vTop, oldV=1 => vBottom
const oldU = (i % 2 === 1) ? 1.0 : 0.0;
const oldV = (i >= 2) ? 1.0 : 0.0;
const newU = oldU > 0.5 ? uRight : uLeft;
const newV = oldV > 0.5 ? vBottom : vTop;
// colorModeArr[i] = 0 => "texture"
data.push(px, py, newU, newV, 0);
}
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
}
// Use program
gl.useProgram(shaderProgram);
// Set up attribute pointers
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
const stride = 5 * 4; // 5 floats per vertex => 20 bytes
gl.enableVertexAttribArray(aPosition);
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, stride, 0);
gl.enableVertexAttribArray(aTexCoord);
gl.vertexAttribPointer(aTexCoord, 2, gl.FLOAT, false, stride, 2 * 4);
gl.enableVertexAttribArray(aColorMode);
gl.vertexAttribPointer(aColorMode, 1, gl.FLOAT, false, stride, 4 * 4);
// Draw
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
frameCount++;
requestAnimationFrame(() => render(columns, rows));
}
})();
</script>
</body>
</html>