Focus With 3D Alien Ring

Focus With 3D Alien Ring script details
Type
Typescript logo image
typescript
App Version
0.19.9
Visibility
public
Date Created
Mar 23, 2025, 2:16:22 PM
Last Edit Date
Mar 23, 2025, 8:44:18 PM

Script Details

The Code
createGUI(); const start = () => { const scene = bitbybit.babylon.scene.getScene(); const engine = scene.getEngine(); const canvas = engine.getRenderingCanvas(); const skyboxOpt = new Bit.Inputs.BabylonScene.SkyboxDto(); skyboxOpt.skybox = Bit.Inputs.Base.skyboxEnum.city; skyboxOpt.blur = 0.6; bitbybit.babylon.scene.enableSkybox(skyboxOpt); const dtoLight = new Bit.Inputs.BabylonScene.DirectionalLightDto(); dtoLight.intensity = 10; dtoLight.shadowBias = 0.002; dtoLight.shadowGeneratorMapSize = 4000; bitbybit.babylon.scene.drawDirectionalLight(dtoLight); const cam = bitbybit.babylon.scene.getActiveCamera() as BABYLON.ArcRotateCamera; cam.position = new BABYLON.Vector3(0, 20, 70); cam.target = new BABYLON.Vector3(0, -5, 0); cam.upperRadiusLimit = 100; cam.lowerRadiusLimit = 20; const boxOpt = new Bit.Inputs.BabylonMeshBuilder.CreateCubeDto(); boxOpt.size = 600; boxOpt.enableShadows = false; const bx = bitbybit.babylon.meshBuilder.createCube(boxOpt); const material1 = new BABYLON.StandardMaterial("x", scene); material1.backFaceCulling = false; material1.diffuseColor = new BABYLON.Color3(0, 0, 0); bx.material = material1; // Torus parameters const R = 14, // Major radius r = 4, // Minor radius step = 3; // Angular step const num_theta = 360 / step; const num_phi = 360 / step; // Generate initial vertex positions let base_positions = []; for (let theta = 0; theta < 360; theta += step) { for (let phi = 0; phi < 360; phi += step) { let tRad = theta * Math.PI / 180; let pRad = phi * Math.PI / 180; let r1 = r * (1 + 0.5 * Math.sin(3 * tRad + 2 * pRad)); let x = (R + r1 * Math.cos(pRad)) * Math.cos(tRad); let y = (R + r1 * Math.cos(pRad)) * Math.sin(tRad); let z = r1 * Math.sin(pRad) + 2 * Math.sin(5 * tRad) * Math.cos(3 * pRad); base_positions.push(x, y, z); } } // Generate indices let indices = []; for (let theta_idx = 0; theta_idx < num_theta; theta_idx++) { for (let phi_idx = 0; phi_idx < num_phi; phi_idx++) { let idx00 = phi_idx * num_theta + theta_idx; let idx10 = phi_idx * num_theta + (theta_idx + 1) % num_theta; let idx01 = ((phi_idx + 1) % num_phi) * num_theta + theta_idx; let idx11 = ((phi_idx + 1) % num_phi) * num_theta + (theta_idx + 1) % num_theta; indices.push(idx00, idx10, idx01); indices.push(idx10, idx11, idx01); } } // Create mesh let vertexData = new BABYLON.VertexData(); vertexData.positions = base_positions.slice(); vertexData.indices = indices; BABYLON.VertexData.ComputeNormals(vertexData.positions, vertexData.indices, vertexData.normals = []); let mesh = new BABYLON.Mesh("custom", scene); vertexData.applyToMesh(mesh, true); // Apply material let material = new BABYLON.PBRMaterial("pbr", scene); material.albedoColor = new BABYLON.Color3(0.1, 0.1, 1); material.metallic = 0.86; material.roughness = 0.14; mesh.material = material; mesh.receiveShadows = true; const sgs = scene.metadata.shadowGenerators; sgs.forEach((s: BABYLON.ShadowGenerator) => { s.addShadowCaster(mesh); }); // Animation parameters const a = 0.2, b = 0.2, c = 0.2; // Wobble speeds const ripple_amplitude = 0.1; const k = 2 * Math.PI / 1; const w = 2 * Math.PI * 2; const d = 0.5; const lifetime = 3; const maxWaveSources = 10; const fadeInTime = 0.3; const fadeOutTime = 1.0; let waveSources = []; let t = 0; let lastWaveTime = 0; // Audio setup const audioContext = new AudioContext(); const oscillator = audioContext.createOscillator(); const gainNode = audioContext.createGain(); const filter = audioContext.createBiquadFilter(); const lfo = audioContext.createOscillator(); const lfoGain = audioContext.createGain(); // Configure audio for mysterious sound oscillator.type = 'sine'; oscillator.frequency.value = 60; // Deep base tone filter.type = 'lowpass'; filter.frequency.value = 200; // Muted initial state filter.Q.value = 3; lfo.type = 'sine'; lfo.frequency.value = 0.5; // Slow eerie modulation lfoGain.gain.value = 50; // Modulation depth gainNode.gain.value = 0.1; // Initial volume // Connect audio nodes lfo.connect(lfoGain); lfoGain.connect(filter.frequency); oscillator.connect(filter); filter.connect(gainNode); gainNode.connect(audioContext.destination); // Start audio oscillator.start(); lfo.start(); // Interaction intensity tracking let interactionIntensity = 0.2; const maxIntensity = 0.1; const intensityDecay = 0.98; // Function to update sound based on interaction function updateSound() { const intensityFactor = interactionIntensity / maxIntensity; oscillator.frequency.value = 60 + intensityFactor * 20; // Slight pitch rise filter.frequency.value = 200 + intensityFactor * 800; // Open filter gainNode.gain.value = 0.1 + intensityFactor * 0.2; // Volume boost lfo.frequency.value = 0.1 + intensityFactor * 0.2 + Math.random() * 0.05; // Chaotic modulation } // Helper function to get initial position of a vertex function getInitialPosition(index) { return new BABYLON.Vector3( base_positions[index * 3], base_positions[index * 3 + 1], base_positions[index * 3 + 2] ); } function getEnvelope(age) { if (age < fadeInTime) { return age / fadeInTime; } else if (age < lifetime - fadeOutTime) { return 1; } else { let fadeOutProgress = (lifetime - age) / fadeOutTime; return fadeOutProgress * Math.exp(1 - 1 / fadeOutProgress); } } // Mouse move handler with sound integration canvas.addEventListener("pointermove", (event) => { let pickResult = scene.pick(event.clientX, event.clientY); if (pickResult.hit && pickResult.pickedMesh === mesh) { if (t - lastWaveTime > 0.2 && waveSources.length < maxWaveSources) { let faceId = pickResult.faceId; let vertexIndex = indices[faceId * 3]; waveSources.push({ vertexIndex: vertexIndex, startTime: t }); lastWaveTime = t; interactionIntensity = Math.min(interactionIntensity + 1, maxIntensity); // Increase sound intensity } } }); // Animation loop let rotationAngle = 0; scene.onBeforeRenderObservable.add(() => { t += scene.getEngine().getDeltaTime() / 1000; waveSources = waveSources.filter(ws => t - ws.startTime < lifetime); // Decay interaction intensity interactionIntensity *= intensityDecay; interactionIntensity = Math.max(interactionIntensity, 0); // Update sound updateSound(); // Compute wobbled positions with z-scaling let current_base_positions = []; const zScale = 1.5; for (let theta = 0; theta < 360; theta += step) { for (let phi = 0; phi < 360; phi += step) { let tRad = theta * Math.PI / 180; let pRad = phi * Math.PI / 180; let r1 = r * (1 + 0.2 * Math.sin(3 * tRad + 2 * pRad + c * t)); let x = (R + r1 * Math.cos(pRad)) * Math.cos(tRad); let y = (R + r1 * Math.cos(pRad)) * Math.sin(tRad); let z_base = r1 * Math.sin(pRad) + 0.5 * Math.sin(5 * tRad + a * t) * Math.cos(3 * pRad + b * t); z_base *= zScale; current_base_positions.push(x, y, z_base); } } // Compute normals for wobbled positions let base_normals = []; BABYLON.VertexData.ComputeNormals(current_base_positions, indices, base_normals); // Apply ripple effect with phasing let final_positions = []; for (let i = 0; i < num_theta * num_phi; i++) { let base_pos = new BABYLON.Vector3( current_base_positions[i * 3], current_base_positions[i * 3 + 1], current_base_positions[i * 3 + 2] ); let normal = new BABYLON.Vector3( base_normals[i * 3], base_normals[i * 3 + 1], base_normals[i * 3 + 2] ); let ripple_term = 0; for (let ws of waveSources) { let pos_i = getInitialPosition(i); let pos_ws = getInitialPosition(ws.vertexIndex); let distance = BABYLON.Vector3.Distance(pos_i, pos_ws); let age = t - ws.startTime; let phase = k * distance - w * age; let envelope = getEnvelope(age); ripple_term += ripple_amplitude * envelope * Math.sin(phase) * Math.exp(-d * distance); } let final_pos = base_pos.add(normal.scale(ripple_term)); final_positions.push(final_pos.x, final_pos.y, final_pos.z); } // Update mesh mesh.updateVerticesData(BABYLON.VertexBuffer.PositionKind, final_positions); let final_normals = []; BABYLON.VertexData.ComputeNormals(final_positions, indices, final_normals); mesh.updateVerticesData(BABYLON.VertexBuffer.NormalKind, final_normals); // Rotate the plane rotationAngle += 0.001; mesh.rotation.y = rotationAngle; }); } 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(); const stackPanel = gui.stackPanel.createStackPanel(stackPanelOpt); const stackPanelOpt2 = new Bit.Inputs.BabylonGui.CreateStackPanelDto(); 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.paddingBottomInPixels = 10; const txtBlockOptions = new Bit.Inputs.BabylonGui.CreateTextBlockDto(); txtBlockOptions.text = "BITBYBIT.DEV"; const txtBlock = gui.textBlock.createTextBlock(txtBlockOptions); txtBlock.height = "60px"; txtBlock.paddingBottomInPixels = 30; stackPanel2.addControl(button); stackPanel2.addControl(image); stackPanel2.addControl(txtBlock1); stackPanel2.addControl(txtBlock); }