createGUI();
const start = () => {
// Get the Babylon.js scene (assuming a BitByBit context or similar setup)
const scene = bitbybit.babylon.scene.getScene();
// Create the plane with increased subdivisions for even smoother waves
const plane = BABYLON.MeshBuilder.CreateGround(
"wavePlane",
{ width: 40, height: 40, subdivisions: 300, updatable: true }, // Increased from 300 to 400
scene
);
// Plane Vertex Shader
BABYLON.Effect.ShadersStore["neonVertexShader"] = `
precision highp float;
attribute vec3 position;
uniform mat4 worldViewProjection;
varying vec2 vPos;
void main() {
gl_Position = worldViewProjection * vec4(position, 1.0);
vPos = position.xz;
}
`;
// Plane Fragment Shader
BABYLON.Effect.ShadersStore["neonFragmentShader"] = `
precision highp float;
varying vec2 vPos;
void main() {
float distance = length(vPos);
if (distance > 20.0) {
discard;
} else {
float normalizedDistance = distance / 20.0;
vec3 blue = vec3(0.0, 0.0, 1.0);
vec3 white = vec3(1.0, 1.0, 1.0);
vec3 color = mix(blue, white, normalizedDistance);
gl_FragColor = vec4(color, 1.0);
}
}
`;
// Create shader material for the plane
const shaderMaterial = new BABYLON.ShaderMaterial(
"neonShader",
scene,
{ vertex: "neon", fragment: "neon" },
{
attributes: ["position"],
uniforms: ["worldViewProjection"]
}
);
shaderMaterial.backFaceCulling = false;
plane.material = shaderMaterial;
// Add glow layer
const glowLayer = new BABYLON.GlowLayer("glow", scene);
glowLayer.intensity = 1.5;
bitbybit.babylon.scene.backgroundColour({
colour: "#ffffff",
});
// Audio Setup
const audioContext = new AudioContext();
let analyser, dataArray, gainNode;
navigator.mediaDevices.getUserMedia({ audio: true })
.then((stream) => {
const source = audioContext.createMediaStreamSource(stream);
gainNode = audioContext.createGain();
analyser = audioContext.createAnalyser();
gainNode.gain.value = 5.0;
analyser.fftSize = 2048;
const bufferLength = analyser.frequencyBinCount;
dataArray = new Uint8Array(bufferLength);
source.connect(gainNode);
gainNode.connect(analyser);
console.log("Microphone connected!");
})
.catch((err) => {
console.error("Microphone error:", err);
alert("Please allow microphone access.");
});
// Animation Parameters
const numBands = 10;
const amplitudeFactor = 6.0; // Increased from 4.0 for taller waves
const baseFreq = 0.5; // Decreased from 1.0 for larger waves
const maxRadius = 20;
let rotationAngle = 0;
// Random phases for waves
const angles = [];
for (let j = 0; j < numBands; j++) {
angles[j] = Math.random() * Math.PI * 2;
}
scene.onBeforeRenderObservable.add(() => {
if (!analyser || !dataArray) return;
analyser.getByteFrequencyData(dataArray);
// Compute amplitudes from audio
const bandSize = Math.floor(dataArray.length / numBands);
const amplitudes = [];
for (let i = 0; i < numBands; i++) {
let sum = 0;
for (let j = 0; j < bandSize; j++) {
sum += dataArray[i * bandSize + j];
}
amplitudes[i] = (sum / bandSize / 255) * amplitudeFactor;
}
// Update plane vertices for wave effect
const positions = plane.getVerticesData(BABYLON.VertexBuffer.PositionKind);
if (!positions) return;
const numVertices = positions.length / 3;
const time = performance.now() / 1000;
for (let i = 0; i < numVertices; i++) {
const x = positions[i * 3];
const z = positions[i * 3 + 2];
const r = Math.sqrt(x * x + z * z);
let yDisp = 0;
if (r <= maxRadius) {
const theta = Math.atan2(z, x);
const scale = Math.pow(1 - r / maxRadius, 2);
for (let j = 0; j < numBands; j++) {
const freq = (j + 1) * baseFreq;
const wave = Math.sin(freq * r + time * 0.5 + angles[j]) * Math.cos((j + 1) * theta);
yDisp += amplitudes[j] * wave;
}
yDisp *= scale;
}
positions[i * 3 + 1] = yDisp;
}
// Rotate the plane
rotationAngle += 0.005;
plane.rotation.y = rotationAngle;
plane.updateVerticesData(BABYLON.VertexBuffer.PositionKind, positions);
plane.refreshBoundingInfo();
});
}
function createGUI() {
const gui = bitbybit.babylon.gui;
const textOpt = new Bit.Inputs.BabylonGui.CreateFullScreenUIDto();
const fullScreenUI = gui.advancedDynamicTexture.createFullScreenUI(textOpt);
const stackPanelOpt = new Bit.Inputs.BabylonGui.CreateStackPanelDto();
stackPanelOpt.background = "#00000000";
const stackPanel = gui.stackPanel.createStackPanel(stackPanelOpt);
const stackPanelOpt2 = new Bit.Inputs.BabylonGui.CreateStackPanelDto();
stackPanelOpt2.background = "#00000000";
const stackPanel2 = gui.stackPanel.createStackPanel(stackPanelOpt2);
stackPanel2.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_BOTTOM;
fullScreenUI.addControl(stackPanel2);
stackPanel.paddingTopInPixels = 10;
stackPanel2.addControl(stackPanel);
const buttonOptions = new Bit.Inputs.BabylonGui.CreateButtonDto();
buttonOptions.width = "400px";
buttonOptions.height = "150px";
buttonOptions.label = "START EXPERIENCE";
buttonOptions.color = "#ffffff";
buttonOptions.background = "#00000000";
const button = gui.button.createSimpleButton(buttonOptions);
button.paddingTopInPixels = 20;
button.paddingBottomInPixels = 20;
button.onPointerClickObservable.add(() => {
start();
})
const imageOpt = new Bit.Inputs.BabylonGui.CreateImageDto();
imageOpt.url = "assets/logo-gold-small.png";
const image = gui.image.createImage(imageOpt);
const padding = 30;
image.paddingTopInPixels = padding;
image.paddingBottomInPixels = padding;
image.paddingLeftInPixels = padding;
image.paddingRightInPixels = padding;
image.onPointerClickObservable.add(() => {
window.open("https://bitbybit.dev", '_blank').focus();
});
const txtBlockOptions1 = new Bit.Inputs.BabylonGui.CreateTextBlockDto();
txtBlockOptions1.text = "Powered By";
const txtBlock1 = gui.textBlock.createTextBlock(txtBlockOptions1);
txtBlock1.height = "40px";
txtBlock1.color = "#ffffff";
txtBlock1.paddingBottomInPixels = 10;
const txtBlockOptions = new Bit.Inputs.BabylonGui.CreateTextBlockDto();
txtBlockOptions.text = "BITBYBIT.DEV";
const txtBlock = gui.textBlock.createTextBlock(txtBlockOptions);
txtBlock.height = "60px";
txtBlock.color = "#ffffff";
txtBlock.paddingBottomInPixels = 30;
stackPanel2.addControl(button);
stackPanel2.addControl(image);
stackPanel2.addControl(txtBlock1);
stackPanel2.addControl(txtBlock);
}