Spaces:
Running
Running
Update test.js
Browse files
test.js
CHANGED
|
@@ -1,205 +1,280 @@
|
|
| 1 |
-
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8" />
|
| 5 |
-
<title>3D Cymatic Display in WebGL2</title>
|
| 6 |
-
<style>
|
| 7 |
-
body, html { margin: 0; height: 100%; overflow: hidden; background: #000; }
|
| 8 |
-
canvas { width: 100%; height: 100%; display: block; }
|
| 9 |
-
</style>
|
| 10 |
-
</head>
|
| 11 |
-
<body>
|
| 12 |
-
<canvas id="canvas"></canvas>
|
| 13 |
-
<!-- Include glMatrix and webgl-utils scripts -->
|
| 14 |
-
<script src="https://cdn.jsdelivr.net/npm/gl-matrix@3.4.3/gl-matrix-min.js"></script>
|
| 15 |
-
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
|
| 16 |
-
<script type="text/javascript">
|
| 17 |
-
"use strict";
|
| 18 |
-
function main() {
|
| 19 |
-
const canvas = document.querySelector("#canvas");
|
| 20 |
-
const gl = canvas.getContext("webgl2");
|
| 21 |
-
if (!gl) {
|
| 22 |
-
console.error("WebGL 2 not supported");
|
| 23 |
-
return;
|
| 24 |
-
}
|
| 25 |
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
out vec3 v_position;
|
| 34 |
-
const float PI = 3.14159;
|
| 35 |
-
void main() {
|
| 36 |
-
// Amplitudes for two modes
|
| 37 |
-
float A1 = 0.1;
|
| 38 |
-
float A2 = 0.05;
|
| 39 |
-
// Mode 1: Fundamental vibration (nodal lines at the boundaries)
|
| 40 |
-
float mode1 = A1 * sin(PI * (a_position.x + 1.0) / 2.0)
|
| 41 |
-
* sin(PI * (a_position.y + 1.0) / 2.0)
|
| 42 |
-
* cos(3.0 * u_time);
|
| 43 |
-
// Mode 2: A higher-order vibration for additional detail
|
| 44 |
-
float mode2 = A2 * sin(2.0 * PI * (a_position.x + 1.0) / 2.0)
|
| 45 |
-
* sin(PI * (a_position.y + 1.0) / 2.0)
|
| 46 |
-
* cos(5.0 * u_time);
|
| 47 |
-
float z = mode1 + mode2;
|
| 48 |
-
|
| 49 |
-
// Compute partial derivatives for normal calculation
|
| 50 |
-
float dx1 = A1 * (PI/2.0) * cos(PI*(a_position.x+1.0)/2.0)
|
| 51 |
-
* sin(PI*(a_position.y+1.0)/2.0) * cos(3.0 * u_time);
|
| 52 |
-
float dx2 = A2 * (2.0*PI/2.0) * cos(2.0*PI*(a_position.x+1.0)/2.0)
|
| 53 |
-
* sin(PI*(a_position.y+1.0)/2.0) * cos(5.0 * u_time);
|
| 54 |
-
float dfdx = dx1 + dx2;
|
| 55 |
-
|
| 56 |
-
float dy1 = A1 * (PI/2.0) * sin(PI*(a_position.x+1.0)/2.0)
|
| 57 |
-
* cos(PI*(a_position.y+1.0)/2.0) * cos(3.0 * u_time);
|
| 58 |
-
float dy2 = A2 * (PI/2.0) * sin(2.0*PI*(a_position.x+1.0)/2.0)
|
| 59 |
-
* cos(PI*(a_position.y+1.0)/2.0) * cos(5.0 * u_time);
|
| 60 |
-
float dfdy = dy1 + dy2;
|
| 61 |
-
|
| 62 |
-
vec3 displacedPos = vec3(a_position.x, a_position.y, z);
|
| 63 |
-
// The normal is computed as the normalized cross of the tangent derivatives.
|
| 64 |
-
// For a height field, an approximate normal is: (-dfdx, -dfdy, 1)
|
| 65 |
-
vec3 normal = normalize(vec3(-dfdx, -dfdy, 1.0));
|
| 66 |
-
|
| 67 |
-
v_normal = normal;
|
| 68 |
-
v_position = displacedPos;
|
| 69 |
-
gl_Position = u_MVP * vec4(displacedPos, 1.0);
|
| 70 |
-
}`;
|
| 71 |
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
void main() {
|
| 81 |
-
vec3 normal = normalize(v_normal);
|
| 82 |
-
vec3 lightDir = normalize(u_lightPos - v_position);
|
| 83 |
-
float diff = max(dot(normal, lightDir), 0.0);
|
| 84 |
-
vec3 ambient = vec3(0.2);
|
| 85 |
-
vec3 diffuse = diff * vec3(0.7, 0.7, 0.8);
|
| 86 |
-
vec3 viewDir = normalize(u_viewPos - v_position);
|
| 87 |
-
vec3 reflectDir = reflect(-lightDir, normal);
|
| 88 |
-
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0);
|
| 89 |
-
vec3 specular = vec3(0.3) * spec;
|
| 90 |
-
vec3 color = ambient + diffuse + specular;
|
| 91 |
-
outColor = vec4(color, 1.0);
|
| 92 |
-
}`;
|
| 93 |
-
|
| 94 |
-
// Create the shader program using webgl-utils
|
| 95 |
-
const program = webglUtils.createProgramFromSources(gl, [vsSource, fsSource]);
|
| 96 |
-
|
| 97 |
-
// Look up attribute and uniform locations
|
| 98 |
-
const positionAttribLocation = gl.getAttribLocation(program, "a_position");
|
| 99 |
-
const timeUniformLocation = gl.getUniformLocation(program, "u_time");
|
| 100 |
-
const mvpUniformLocation = gl.getUniformLocation(program, "u_MVP");
|
| 101 |
-
const lightPosUniformLocation = gl.getUniformLocation(program, "u_lightPos");
|
| 102 |
-
const viewPosUniformLocation = gl.getUniformLocation(program, "u_viewPos");
|
| 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 |
-
// Create and bind a Vertex Array Object
|
| 130 |
-
const vao = gl.createVertexArray();
|
| 131 |
-
gl.bindVertexArray(vao);
|
| 132 |
-
|
| 133 |
-
// Create and fill the position buffer
|
| 134 |
-
const positionBuffer = gl.createBuffer();
|
| 135 |
-
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
| 136 |
-
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
|
| 137 |
-
gl.enableVertexAttribArray(positionAttribLocation);
|
| 138 |
-
gl.vertexAttribPointer(positionAttribLocation, 3, gl.FLOAT, false, 0, 0);
|
| 139 |
-
|
| 140 |
-
// Create and fill the index buffer
|
| 141 |
-
const indexBuffer = gl.createBuffer();
|
| 142 |
-
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
|
| 143 |
-
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(indices), gl.STATIC_DRAW);
|
| 144 |
-
|
| 145 |
-
// Set up camera matrices using glMatrix
|
| 146 |
-
const fieldOfView = 45 * Math.PI / 180;
|
| 147 |
-
const near = 0.1;
|
| 148 |
-
const far = 100;
|
| 149 |
-
let projectionMatrix = glMatrix.mat4.create();
|
| 150 |
-
let viewMatrix = glMatrix.mat4.create();
|
| 151 |
-
let modelMatrix = glMatrix.mat4.create();
|
| 152 |
-
const eye = [0, -2.5, 2.5]; // Camera position
|
| 153 |
-
const center = [0, 0, 0]; // Look at center of plate
|
| 154 |
-
const up = [0, 0, 1];
|
| 155 |
-
glMatrix.mat4.perspective(projectionMatrix, fieldOfView, canvas.clientWidth / canvas.clientHeight, near, far);
|
| 156 |
-
glMatrix.mat4.lookAt(viewMatrix, eye, center, up);
|
| 157 |
-
// Optionally, rotate the model slightly for a dramatic view
|
| 158 |
-
glMatrix.mat4.rotateX(modelMatrix, modelMatrix, -0.5);
|
| 159 |
-
let mvpMatrix = glMatrix.mat4.create();
|
| 160 |
-
glMatrix.mat4.multiply(mvpMatrix, viewMatrix, modelMatrix);
|
| 161 |
-
glMatrix.mat4.multiply(mvpMatrix, projectionMatrix, mvpMatrix);
|
| 162 |
-
|
| 163 |
-
// Set light and view positions (for the shader)
|
| 164 |
-
const lightPos = [2.0, -2.0, 3.0];
|
| 165 |
-
const viewPos = eye;
|
| 166 |
-
|
| 167 |
-
// Animation loop
|
| 168 |
-
let startTime = null;
|
| 169 |
-
function render(now) {
|
| 170 |
-
if (!startTime) startTime = now;
|
| 171 |
-
const timeInSeconds = (now - startTime) * 0.001;
|
| 172 |
-
|
| 173 |
-
// Resize canvas if needed
|
| 174 |
-
webglUtils.resizeCanvasToDisplaySize(gl.canvas);
|
| 175 |
-
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
|
| 176 |
-
gl.enable(gl.DEPTH_TEST);
|
| 177 |
-
gl.clearColor(0.0, 0.0, 0.0, 1.0);
|
| 178 |
-
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
| 179 |
-
|
| 180 |
-
// Update the projection in case the canvas size changed
|
| 181 |
-
glMatrix.mat4.perspective(projectionMatrix, fieldOfView, gl.canvas.clientWidth / gl.canvas.clientHeight, near, far);
|
| 182 |
-
glMatrix.mat4.lookAt(viewMatrix, eye, center, up);
|
| 183 |
-
glMatrix.mat4.multiply(mvpMatrix, viewMatrix, modelMatrix);
|
| 184 |
-
glMatrix.mat4.multiply(mvpMatrix, projectionMatrix, mvpMatrix);
|
| 185 |
-
|
| 186 |
-
// Use our program and bind the VAO
|
| 187 |
-
gl.useProgram(program);
|
| 188 |
-
gl.bindVertexArray(vao);
|
| 189 |
-
|
| 190 |
-
// Set shader uniforms
|
| 191 |
-
gl.uniform1f(timeUniformLocation, timeInSeconds);
|
| 192 |
-
gl.uniformMatrix4fv(mvpUniformLocation, false, mvpMatrix);
|
| 193 |
-
gl.uniform3fv(lightPosUniformLocation, lightPos);
|
| 194 |
-
gl.uniform3fv(viewPosUniformLocation, viewPos);
|
| 195 |
-
|
| 196 |
-
// Draw the grid
|
| 197 |
-
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_INT, 0);
|
| 198 |
-
requestAnimationFrame(render);
|
| 199 |
-
}
|
| 200 |
-
requestAnimationFrame(render);
|
| 201 |
}
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use strict";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
+
function main() {
|
| 4 |
+
// Get a WebGL2 context
|
| 5 |
+
const canvas = document.querySelector("#canvas");
|
| 6 |
+
const gl = canvas.getContext("webgl2");
|
| 7 |
+
if (!gl) {
|
| 8 |
+
return;
|
| 9 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
+
//ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 12 |
+
// Vertex shader: simply pass the vertex positions along.
|
| 13 |
+
const vs = `#version 300 es
|
| 14 |
+
in vec4 a_position;
|
| 15 |
+
void main() {
|
| 16 |
+
gl_Position = a_position;
|
| 17 |
+
}
|
| 18 |
+
`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
+
//ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 21 |
+
// Fragment shader: a scientificallyβinspired 3D cymatic display.
|
| 22 |
+
// This shader rayβmarches a vibrating βplateβ (whose height is defined
|
| 23 |
+
// by the sum of two sinusoidal (mode) functions) and then shades it with
|
| 24 |
+
// diffuse and specular lighting. The palette() function is used to inject
|
| 25 |
+
// a pleasing color variation based on the local vibration amplitude.
|
| 26 |
+
const fs = `#version 300 es
|
| 27 |
+
precision highp float;
|
| 28 |
+
|
| 29 |
+
uniform vec2 iResolution;
|
| 30 |
+
uniform vec2 iMouse;
|
| 31 |
+
uniform float iTime;
|
| 32 |
+
out vec4 outColor;
|
| 33 |
+
|
| 34 |
+
// A color palette function (from Shadertoy) to add some βpopβ
|
| 35 |
+
vec3 palette( float t ) {
|
| 36 |
+
vec3 a = vec3(0.5, 0.5, 0.5);
|
| 37 |
+
vec3 b = vec3(0.5, 0.5, 0.5);
|
| 38 |
+
vec3 c = vec3(1.0, 1.0, 1.0);
|
| 39 |
+
vec3 d = vec3(0.263, 0.416, 0.557);
|
| 40 |
+
return a + b * cos( 6.28318 * (c * t + d) );
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
// The vibrating plate β defined on the xzβplane (with x,z in [-1,1])
|
| 44 |
+
// and with vertical displacement given by y = plate(x,z,t).
|
| 45 |
+
// Two modes are added (a βfundamentalβ and a secondβharmonic mode) to mimic
|
| 46 |
+
// realistic cymatic (Chladni) patterns on a clamped plate.
|
| 47 |
+
float plate(vec2 pos, float t) {
|
| 48 |
+
// Map pos from [-1,1] to [0,1] (for clampedβedge conditions)
|
| 49 |
+
vec2 uv = (pos + 1.0) * 0.5;
|
| 50 |
+
float mode1 = sin(3.14159 * uv.x) * sin(3.14159 * uv.y) * cos(3.14159 * t);
|
| 51 |
+
float mode2 = sin(2.0 * 3.14159 * uv.x) * sin(2.0 * 3.14159 * uv.y) * cos(2.0 * 3.14159 * t);
|
| 52 |
+
return 0.2 * (mode1 + mode2);
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
// Compute the normal of the heightfield (the vibrating plate) using finite differences.
|
| 56 |
+
vec3 calcNormal(vec2 pos, float t) {
|
| 57 |
+
float eps = 0.001;
|
| 58 |
+
float h = plate(pos, t);
|
| 59 |
+
float hx = plate(pos + vec2(eps, 0.0), t) - h;
|
| 60 |
+
float hz = plate(pos + vec2(0.0, eps), t) - h;
|
| 61 |
+
return normalize(vec3(-hx, 1.0, -hz));
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
// Given a 3D point p, return its vertical distance to the plate surface.
|
| 65 |
+
// (If p is exactly on the surface then p.y = plate(p.xz,t) and the result is zero.)
|
| 66 |
+
float mapHeight(vec3 p, float t) {
|
| 67 |
+
// Outside the domain x,z β [-1,1] we assume a flat floor at y=0.
|
| 68 |
+
if (abs(p.x) > 1.0 || abs(p.z) > 1.0) {
|
| 69 |
+
return p.y;
|
| 70 |
+
}
|
| 71 |
+
return p.y - plate(vec2(p.x, p.z), t);
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
// A simple raycast function that marches a ray from the camera and
|
| 75 |
+
// returns the distance along the ray at which the plate is hit.
|
| 76 |
+
float raycast(vec3 ro, vec3 rd, float t) {
|
| 77 |
+
float tMin = 0.0;
|
| 78 |
+
float tMax = 20.0;
|
| 79 |
+
float tCurrent = tMin;
|
| 80 |
+
float stepSize = 0.02;
|
| 81 |
+
bool hit = false;
|
| 82 |
+
for (int i = 0; i < 500; i++) {
|
| 83 |
+
vec3 pos = ro + rd * tCurrent;
|
| 84 |
+
float d = mapHeight(pos, t);
|
| 85 |
+
if (d < 0.001) {
|
| 86 |
+
hit = true;
|
| 87 |
+
break;
|
| 88 |
}
|
| 89 |
+
tCurrent += stepSize;
|
| 90 |
+
if (tCurrent > tMax) break;
|
| 91 |
+
}
|
| 92 |
+
if (!hit) return -1.0;
|
| 93 |
+
// Refine the hit point with a short binary search.
|
| 94 |
+
float tA = tCurrent - stepSize;
|
| 95 |
+
float tB = tCurrent;
|
| 96 |
+
for (int i = 0; i < 10; i++) {
|
| 97 |
+
float tMid = (tA + tB) * 0.5;
|
| 98 |
+
float dMid = mapHeight(ro + rd * tMid, t);
|
| 99 |
+
if (dMid > 0.0) {
|
| 100 |
+
tA = tMid;
|
| 101 |
+
} else {
|
| 102 |
+
tB = tMid;
|
| 103 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
}
|
| 105 |
+
return (tA + tB) * 0.5;
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
void main() {
|
| 109 |
+
// Compute normalized screen coordinates (centered on 0)
|
| 110 |
+
vec2 uv = (gl_FragCoord.xy - 0.5 * iResolution.xy) / iResolution.y;
|
| 111 |
+
|
| 112 |
+
// Use the mouse to control the cameraβs azimuth and pitch.
|
| 113 |
+
// Horizontal movement rotates 0β2Ο; vertical movement adjusts pitch.
|
| 114 |
+
float angle = iMouse.x / iResolution.x * 6.28318; // full rotation
|
| 115 |
+
float pitch = mix(0.4, 1.2, iMouse.y / iResolution.y);
|
| 116 |
+
float radius = 4.0;
|
| 117 |
+
vec3 ro = vec3(
|
| 118 |
+
radius * cos(pitch) * cos(angle),
|
| 119 |
+
radius * sin(pitch),
|
| 120 |
+
radius * cos(pitch) * sin(angle)
|
| 121 |
+
);
|
| 122 |
+
vec3 target = vec3(0.0, 0.0, 0.0);
|
| 123 |
+
|
| 124 |
+
// Construct a simple camera coordinate system.
|
| 125 |
+
vec3 forward = normalize(target - ro);
|
| 126 |
+
vec3 right = normalize(cross(forward, vec3(0.0, 1.0, 0.0)));
|
| 127 |
+
vec3 up = cross(right, forward);
|
| 128 |
+
|
| 129 |
+
// Compute the ray direction using a basic perspective projection.
|
| 130 |
+
vec3 rd = normalize(forward + uv.x * right + uv.y * up);
|
| 131 |
+
|
| 132 |
+
// March the ray to see if and where it hits the vibrating plate.
|
| 133 |
+
float tHit = raycast(ro, rd, iTime);
|
| 134 |
+
vec3 color;
|
| 135 |
+
if (tHit > 0.0) {
|
| 136 |
+
vec3 pos = ro + rd * tHit;
|
| 137 |
+
// Get the local normal from the heightfield
|
| 138 |
+
vec3 normal = calcNormal(vec2(pos.x, pos.z), iTime);
|
| 139 |
+
|
| 140 |
+
// Standard lighting: diffuse + specular
|
| 141 |
+
vec3 lightDir = normalize(vec3(0.5, 1.0, 0.8));
|
| 142 |
+
float diff = max(dot(normal, lightDir), 0.0);
|
| 143 |
+
vec3 viewDir = normalize(ro - pos);
|
| 144 |
+
vec3 halfDir = normalize(lightDir + viewDir);
|
| 145 |
+
float spec = pow(max(dot(normal, halfDir), 0.0), 32.0);
|
| 146 |
+
|
| 147 |
+
// Base color comes from the palette β modulated by the local vibration amplitude.
|
| 148 |
+
float h = plate(vec2(pos.x, pos.z), iTime);
|
| 149 |
+
vec3 baseColor = palette(h * 5.0);
|
| 150 |
+
|
| 151 |
+
color = baseColor * diff + vec3(0.1) * spec + vec3(0.1);
|
| 152 |
+
} else {
|
| 153 |
+
// If no hit, use a subtle background gradient.
|
| 154 |
+
color = mix(vec3(0.0, 0.0, 0.1), vec3(0.0), uv.y + 0.5);
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
outColor = vec4(color, 1.0);
|
| 158 |
+
}
|
| 159 |
+
`;
|
| 160 |
+
|
| 161 |
+
//βββββββββββββββββββββββββββββββββββββββββββοΏ½οΏ½ββββββββββββββββ
|
| 162 |
+
// Create and compile the shader program using webgl-utils.
|
| 163 |
+
const program = webglUtils.createProgramFromSources(gl, [vs, fs]);
|
| 164 |
+
|
| 165 |
+
// Look up attribute and uniform locations.
|
| 166 |
+
const positionAttributeLocation = gl.getAttribLocation(program, "a_position");
|
| 167 |
+
const resolutionLocation = gl.getUniformLocation(program, "iResolution");
|
| 168 |
+
const mouseLocation = gl.getUniformLocation(program, "iMouse");
|
| 169 |
+
const timeLocation = gl.getUniformLocation(program, "iTime");
|
| 170 |
+
|
| 171 |
+
// Create a vertex array object (VAO) and bind it.
|
| 172 |
+
const vao = gl.createVertexArray();
|
| 173 |
+
gl.bindVertexArray(vao);
|
| 174 |
+
|
| 175 |
+
// Create a buffer and put a fullβscreen quad in it.
|
| 176 |
+
const positionBuffer = gl.createBuffer();
|
| 177 |
+
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
| 178 |
+
gl.bufferData(
|
| 179 |
+
gl.ARRAY_BUFFER,
|
| 180 |
+
new Float32Array([
|
| 181 |
+
-1, -1,
|
| 182 |
+
1, -1,
|
| 183 |
+
-1, 1,
|
| 184 |
+
-1, 1,
|
| 185 |
+
1, -1,
|
| 186 |
+
1, 1,
|
| 187 |
+
]),
|
| 188 |
+
gl.STATIC_DRAW
|
| 189 |
+
);
|
| 190 |
+
|
| 191 |
+
// Enable the position attribute.
|
| 192 |
+
gl.enableVertexAttribArray(positionAttributeLocation);
|
| 193 |
+
gl.vertexAttribPointer(
|
| 194 |
+
positionAttributeLocation,
|
| 195 |
+
2, // 2 components per vertex
|
| 196 |
+
gl.FLOAT, // data type is float
|
| 197 |
+
false,
|
| 198 |
+
0,
|
| 199 |
+
0
|
| 200 |
+
);
|
| 201 |
+
|
| 202 |
+
//ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 203 |
+
// Setup mouse / touch interactions.
|
| 204 |
+
const playpauseElem = document.querySelector(".playpause");
|
| 205 |
+
const inputElem = document.querySelector(".divcanvas");
|
| 206 |
+
inputElem.addEventListener("mouseover", requestFrame);
|
| 207 |
+
inputElem.addEventListener("mouseout", cancelFrame);
|
| 208 |
+
|
| 209 |
+
let mouseX = 0;
|
| 210 |
+
let mouseY = 0;
|
| 211 |
+
function setMousePosition(e) {
|
| 212 |
+
const rect = inputElem.getBoundingClientRect();
|
| 213 |
+
mouseX = e.clientX - rect.left;
|
| 214 |
+
mouseY = rect.height - (e.clientY - rect.top) - 1;
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
inputElem.addEventListener("mousemove", setMousePosition);
|
| 218 |
+
inputElem.addEventListener("touchstart", (e) => {
|
| 219 |
+
e.preventDefault();
|
| 220 |
+
playpauseElem.classList.add("playpausehide");
|
| 221 |
+
requestFrame();
|
| 222 |
+
}, { passive: false });
|
| 223 |
+
inputElem.addEventListener("touchmove", (e) => {
|
| 224 |
+
e.preventDefault();
|
| 225 |
+
setMousePosition(e.touches[0]);
|
| 226 |
+
}, { passive: false });
|
| 227 |
+
inputElem.addEventListener("touchend", (e) => {
|
| 228 |
+
e.preventDefault();
|
| 229 |
+
playpauseElem.classList.remove("playpausehide");
|
| 230 |
+
cancelFrame();
|
| 231 |
+
}, { passive: false });
|
| 232 |
+
|
| 233 |
+
//ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 234 |
+
// Animation loop variables and functions.
|
| 235 |
+
let requestId;
|
| 236 |
+
function requestFrame() {
|
| 237 |
+
if (!requestId) {
|
| 238 |
+
requestId = requestAnimationFrame(render);
|
| 239 |
+
}
|
| 240 |
+
}
|
| 241 |
+
function cancelFrame() {
|
| 242 |
+
if (requestId) {
|
| 243 |
+
cancelAnimationFrame(requestId);
|
| 244 |
+
requestId = undefined;
|
| 245 |
+
}
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
let then = 0;
|
| 249 |
+
let time = 0;
|
| 250 |
+
function render(now) {
|
| 251 |
+
requestId = undefined;
|
| 252 |
+
now *= 0.001; // convert milliseconds to seconds
|
| 253 |
+
const elapsedTime = Math.min(now - then, 0.1);
|
| 254 |
+
time += elapsedTime;
|
| 255 |
+
then = now;
|
| 256 |
+
|
| 257 |
+
// Resize canvas if needed.
|
| 258 |
+
webglUtils.resizeCanvasToDisplaySize(gl.canvas);
|
| 259 |
+
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
|
| 260 |
+
|
| 261 |
+
// Use our program and bind our VAO.
|
| 262 |
+
gl.useProgram(program);
|
| 263 |
+
gl.bindVertexArray(vao);
|
| 264 |
+
|
| 265 |
+
// Set the uniforms.
|
| 266 |
+
gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height);
|
| 267 |
+
gl.uniform2f(mouseLocation, mouseX, mouseY);
|
| 268 |
+
gl.uniform1f(timeLocation, time);
|
| 269 |
+
|
| 270 |
+
// Draw the fullβscreen quad.
|
| 271 |
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
| 272 |
+
|
| 273 |
+
requestFrame();
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
requestFrame();
|
| 277 |
+
requestAnimationFrame(cancelFrame);
|
| 278 |
+
}
|
| 279 |
+
|
| 280 |
+
main();
|