3D Field Of AI Flowers

3D Field Of AI Flowers script details
Type
Typescript logo image
typescript
App Version
0.19.7
Visibility
public
Date Created
Mar 7, 2025, 1:39:22 PM
Last Edit Date
Mar 20, 2025, 12:19:45 PM

Script Details

The Code
createGUI(); const start = async () => { // Assuming BabylonJS is already included and scene is initialized const scene = bitbybit.babylon.scene.getScene(); const cam = bitbybit.babylon.scene.getActiveCamera() as BABYLON.ArcRotateCamera; cam.position = new BABYLON.Vector3(10, 70, 10); // ### AudioContext Setup const audioCtx = new AudioContext(); const oscillatorPool = []; for (let i = 0; i < 5; i++) { const osc = audioCtx.createOscillator(); osc.type = 'sine'; const gain = audioCtx.createGain(); gain.gain.setValueAtTime(0, audioCtx.currentTime); osc.connect(gain); gain.connect(audioCtx.destination); const vibratoOsc = audioCtx.createOscillator(); vibratoOsc.type = 'sine'; vibratoOsc.frequency.setValueAtTime(5, audioCtx.currentTime); const vibratoGain = audioCtx.createGain(); vibratoGain.gain.setValueAtTime(10, audioCtx.currentTime); vibratoOsc.connect(vibratoGain); vibratoGain.connect(osc.frequency); osc.start(); vibratoOsc.start(); oscillatorPool.push({ osc, gain, inUse: false }); } function getAvailableOscillator() { return oscillatorPool.find(oscData => !oscData.inUse) || null; } function modulateSound(osc, gain, frequency, isOpening) { osc.frequency.setValueAtTime(frequency, audioCtx.currentTime); if (isOpening) { gain.gain.cancelScheduledValues(audioCtx.currentTime); gain.gain.setValueAtTime(0, audioCtx.currentTime); gain.gain.linearRampToValueAtTime(0.7, audioCtx.currentTime + 0.1); gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.8); } else { gain.gain.cancelScheduledValues(audioCtx.currentTime); gain.gain.setValueAtTime(0, audioCtx.currentTime); gain.gain.linearRampToValueAtTime(0.5, audioCtx.currentTime + 0.2); gain.gain.linearRampToValueAtTime(0, audioCtx.currentTime + 0.6); } } // ### Shader Definitions for Petal Gradients BABYLON.Effect.ShadersStore["petalVertexShader"] = ` precision highp float; attribute vec3 position; attribute vec2 uv; uniform mat4 worldViewProjection; varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; } `; BABYLON.Effect.ShadersStore["petalFragmentShader"] = ` precision highp float; varying vec2 vUV; uniform float invertGradient; void main(void) { float t = invertGradient > 0.5 ? 1.0 - vUV.y : vUV.y; vec3 color; if (t < 0.5) { color = mix(vec3(1, 0.0, 1), vec3(0.0, 0.0, 1.0), t * 2.0); } else { color = mix(vec3(0.0, 0.0, 1.0), vec3(1.0, 1.0, 1.0), (t - 0.5) * 2.0); } gl_FragColor = vec4(color, 1.0); } `; // ### Create Shader Materials const petalMaterialNormal = new BABYLON.ShaderMaterial("petalShaderNormal", scene, { vertex: "petal", fragment: "petal" }, { attributes: ["position", "uv"], uniforms: ["worldViewProjection", "invertGradient"] }); petalMaterialNormal.setFloat("invertGradient", 0.0); const petalMaterialInverted = new BABYLON.ShaderMaterial("petalShaderInverted", scene, { vertex: "petal", fragment: "petal" }, { attributes: ["position", "uv"], uniforms: ["worldViewProjection", "invertGradient"] }); petalMaterialInverted.setFloat("invertGradient", 1.0); // ### Petal Creation Function function createPetal(scene) { const points = [ new BABYLON.Vector3(0, 0, 0), new BABYLON.Vector3(0.15, 0.3, 0), new BABYLON.Vector3(0.3, 0.6, 0), new BABYLON.Vector3(0.4, 0.9, 0), new BABYLON.Vector3(0.5, 1.2, 0), new BABYLON.Vector3(0.6, 0.9, 0), new BABYLON.Vector3(0.7, 0.6, 0) ]; const tube = BABYLON.MeshBuilder.CreateTube("petalTube", { path: points, radius: 0.03, tessellation: 3, cap: BABYLON.Mesh.CAP_ALL, sideOrientation: BABYLON.Mesh.DOUBLESIDE, updatable: false }, scene); return tube; } // ### Flower Creation Function function createFlower(scene) { const flower = new BABYLON.TransformNode("flower", scene); const scale = 0.5 + Math.random() * 5.5; // Scale between 0.5 and 6.0 flower.scaling = new BABYLON.Vector3(scale, scale, scale); let position; let attempts = 0; do { position = new BABYLON.Vector3(Math.random() * 100 - 50, 0, Math.random() * 100 - 50); // Smaller 100x100 field attempts++; if (attempts > 10000) { console.warn("Could not find non-overlapping position after 10000 attempts"); break; } } while (flowerDataArray.some(fd => BABYLON.Vector3.Distance(position, fd.position) < (scale + fd.scale) * 1.2)); // Adjusted overlap check const amp_x = 0.05 + Math.random() * 0.2; const amp_z = 0.05 + Math.random() * 0.2; const amp_y = 0.05 + Math.random() * 0.2; const phase_x = Math.random() * Math.PI * 2; const phase_z = Math.random() * Math.PI * 2; const phase_y = Math.random() * Math.PI * 2; const basePosition = position.clone(); basePosition.y = amp_y; flower.position = basePosition.clone(); const petalNodes = []; const petalCount = 24; const invertGradient = Math.random() < 0.5; // Per-flower gradient decision const petalMaterial = invertGradient ? petalMaterialInverted : petalMaterialNormal; for (let i = 0; i < petalCount; i++) { const petalTube = createPetal(scene); petalTube.material = petalMaterial; // Consistent material for all petals petalTube.metadata = { flower: flower }; const petalNode = new BABYLON.TransformNode("petalNode", scene); petalNode.rotation.y = (i / petalCount) * Math.PI * 2; petalTube.parent = petalNode; petalNode.parent = flower; petalNodes.push(petalTube); } const flowerData = { flower, basePosition, amp_x, amp_z, amp_y, phase_x, phase_z, phase_y, freq: 0.5 + Math.random(), scale, petalNodes, position, // For overlap check isAnimating: false, animateFullCycle: function () { if (this.isAnimating) return; const oscData = getAvailableOscillator(); if (!oscData) return; oscData.inUse = true; modulateSound(oscData.osc, oscData.gain, 440 / this.scale, true); this.isAnimating = true; const openAnim = createBloomAnimation(true); const closeAnim = createBloomAnimation(false); scene.beginDirectAnimation(this.petalNodes, [openAnim], 0, 60, false, 1, () => { modulateSound(oscData.osc, oscData.gain, 330 / this.scale, false); scene.beginDirectAnimation(this.petalNodes, [closeAnim], 0, 60, false, 1, () => { this.isAnimating = false; // Reset flag for re-triggering oscData.inUse = false; oscData.gain.gain.linearRampToValueAtTime(0, audioCtx.currentTime + 0.2); }); }); } }; return flowerData; } // ### Bloom Animation Function function createBloomAnimation(isOpening) { const animation = new BABYLON.Animation( "bloom", "rotation.x", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT ); const keys = isOpening ? [{ frame: 0, value: 0 }, { frame: 60, value: -Math.PI / 4 }] : [{ frame: 0, value: -Math.PI / 4 }, { frame: 60, value: 0 }]; animation.setKeys(keys); const easing = new BABYLON.SineEase(); easing.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEINOUT); animation.setEasingFunction(easing); return animation; } // ### Create Flowers and Wind Animation const flowerDataArray = []; for (let i = 0; i < 250; i++) { const flowerData = createFlower(scene); flowerDataArray.push(flowerData); } scene.registerBeforeRender(() => { const time = performance.now() / 1000; flowerDataArray.forEach(flowerData => { if (flowerData && flowerData.flower && flowerData.basePosition) { const { basePosition, amp_x, amp_z, amp_y, phase_x, phase_z, phase_y, freq } = flowerData; const x = basePosition.x + amp_x * Math.sin(freq * time + phase_x); const y = basePosition.y + amp_y * Math.sin(freq * time + phase_y); const z = basePosition.z + amp_z * Math.sin(freq * time + phase_z); flowerData.flower.position.set(x, y, z); } else { console.warn("Skipping invalid flower data:", flowerData); } }); }); // ### Event Listeners for Interaction const hoveredFlowers = new Set(); scene.onPointerMove = () => { const pickResult = scene.pick(scene.pointerX, scene.pointerY); if (pickResult.hit && pickResult.pickedMesh.metadata && pickResult.pickedMesh.metadata.flower) { const flower = pickResult.pickedMesh.metadata.flower; const flowerData = flowerDataArray.find(fd => fd.flower === flower); if (flowerData && !flowerData.isAnimating && !hoveredFlowers.has(flower)) { hoveredFlowers.add(flower); flowerData.animateFullCycle(); } } else { hoveredFlowers.clear(); // Clear if not hovering over a flower } }; scene.onPointerUp = () => { if (hoveredFlowers.size === 0) { if (oscillatorPool) { oscillatorPool.forEach(oscData => { oscData.gain.gain.linearRampToValueAtTime(0, audioCtx.currentTime + 0.2); }); } } }; } 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); }