Touch the grass

Touch the grass script details
Type
Typescript logo image
typescript
App Version
0.19.5
Visibility
public
Date Created
Feb 26, 2025, 8:02:24 AM
Last Edit Date
Mar 5, 2025, 10:02:25 AM

Script Details

The Code
async function start() { const scene = bitbybit.babylon.scene.getScene(); const engine = bitbybit.babylon.engine.getEngine(); // **Constants** // **Constants** const numWeeds = 3000; // Number of weeds const numSegments = 5; // Segments per weed const segmentLength = 0.4; // Length of each segment const weedHeight = numSegments * segmentLength; // Total height = 2 units const mouseEffectRadius = 3; // Radius of mouse wind influence const maxMouseInfluence = 1; // Maximum bending strength const recoveryAlpha = 0.1; // Recovery speed (lower = slower) const velocityInfluenceFactor = 2; // Mouse speed effect // **Create invisible ground at the top of the weeds** const ground = BABYLON.MeshBuilder.CreateGround("ground", { width: 100, height: 100 }, scene); ground.position.y = weedHeight; // Ground at weed tops (y = 2) ground.visibility = 0; // Invisible but pickable // **Weed creation** const weeds = []; const baseColor = new BABYLON.Color4(0.5, 0, 1, 1); // Purple at base const midColor = new BABYLON.Color4(0, 0, 1, 1); // Blue in middle const topColor = new BABYLON.Color4(1, 1, 1, 1); // White at top for (let i = 0; i < numWeeds; i++) { const x = (Math.random() - 0.5) * 12; const z = (Math.random() - 0.5) * 30; const basePosition = new BABYLON.Vector3(x, 0, z); const points = []; const colors = []; for (let k = 0; k <= numSegments; k++) { points.push(new BABYLON.Vector3(x, k * segmentLength, z)); const t = k / numSegments; let color; if (t < 0.5) { color = BABYLON.Color4.Lerp(baseColor, midColor, t * 2); } else { color = BABYLON.Color4.Lerp(midColor, topColor, (t - 0.5) * 2); } colors.push(color); } const linesMesh = BABYLON.MeshBuilder.CreateLines("weed" + i, { points: points, colors: colors, updatable: true }, scene); linesMesh.isPickable = false; weeds.push({ basePosition, linesMesh, randomOffset: Math.random() * Math.PI * 2, smoothedMouseInfluence: 0, smoothedMouseDirection: new BABYLON.Vector3(0, 0, 0) }); } // **Mouse and time tracking** let time = 0; let mouseWorldPos = new BABYLON.Vector3(0, weedHeight, 0); // Start at weed top height let lastMouseWorldPos = mouseWorldPos.clone(); let mouseVelocity = new BABYLON.Vector3(0, 0, 0); // **Mouse movement handler** scene.onPointerObservable.add((pointerInfo) => { if (pointerInfo.type === BABYLON.PointerEventTypes.POINTERMOVE && pointerInfo.event.buttons === 0) { const pickInfo = scene.pick(pointerInfo.event.clientX, pointerInfo.event.clientY); if (pickInfo.hit && pickInfo.pickedMesh === ground) { lastMouseWorldPos = mouseWorldPos.clone(); mouseWorldPos = pickInfo.pickedPoint; mouseVelocity = mouseWorldPos.subtract(lastMouseWorldPos); } } }); // **Render loop** engine.runRenderLoop(() => { time += 0.02; weeds.forEach(weed => { // **Mouse influence as radial wind** const weedTop = weed.basePosition.clone(); weedTop.y = weedHeight; const fromWeedTopToMouse = mouseWorldPos.subtract(weedTop); // Direction away from weed top const distance = fromWeedTopToMouse.length(); const falloff = (distance < mouseEffectRadius) ? (1 - distance / mouseEffectRadius) : 0; // Directional influence from mouse velocity const velocityNorm = mouseVelocity.length() > 0 ? mouseVelocity.normalize() : new BABYLON.Vector3(0, 0, 0); const toMouseNorm = fromWeedTopToMouse.length() > 0 ? fromWeedTopToMouse.normalize() : new BABYLON.Vector3(0, 0, 0); const velocityDot = BABYLON.Vector3.Dot(velocityNorm, toMouseNorm); const velocityInfluence = Math.max(0, velocityDot) * mouseVelocity.length() * velocityInfluenceFactor; // Total target influence const targetMouseInfluence = falloff * maxMouseInfluence + velocityInfluence; // **Smooth recovery** weed.smoothedMouseInfluence = BABYLON.Scalar.Lerp( weed.smoothedMouseInfluence, targetMouseInfluence, recoveryAlpha ); const targetDirection = fromWeedTopToMouse.length() > 0 ? fromWeedTopToMouse.normalize() : new BABYLON.Vector3(0, 0, 0); weed.smoothedMouseDirection = BABYLON.Vector3.Lerp( weed.smoothedMouseDirection, targetDirection, recoveryAlpha ); // **Build weed segments** const points = []; let currentPoint = weed.basePosition.clone(); points.push(currentPoint.clone()); const windVariation = Math.sin(time + weed.randomOffset) * 0.1; const windStrength = 0.05 + windVariation; const windAxis = new BABYLON.Vector3(1, 0, 0); let mouseAxis = null; if (weed.smoothedMouseInfluence > 0 && weed.smoothedMouseDirection.length() > 0) { mouseAxis = BABYLON.Vector3.Cross(BABYLON.Axis.Y, weed.smoothedMouseDirection); } // Accumulate angles to ensure bottom leans less let accumulatedMouseAngle = 0; for (let k = 1; k <= numSegments; k++) { const windAngle = windStrength * k; const windMatrix = BABYLON.Matrix.RotationAxis(windAxis, windAngle); let segmentDir = new BABYLON.Vector3(0, segmentLength, 0); if (mouseAxis && weed.smoothedMouseInfluence > 0) { // Incremental angle increase from base to top, leaning away const mouseInfluenceFactor = k / numSegments; // 0.2 at k=1, 1 at k=5 const mouseAngleIncrement = weed.smoothedMouseInfluence * mouseInfluenceFactor * 0.2; accumulatedMouseAngle += mouseAngleIncrement; // Add to previous angle const mouseMatrix = BABYLON.Matrix.RotationAxis(mouseAxis, accumulatedMouseAngle); // Positive for away segmentDir = BABYLON.Vector3.TransformCoordinates(segmentDir, mouseMatrix); } // Apply wind after mouse bending segmentDir = BABYLON.Vector3.TransformCoordinates(segmentDir, windMatrix); currentPoint = currentPoint.add(segmentDir); points.push(currentPoint.clone()); } const positions = points.reduce((acc, p) => acc.concat([p.x, p.y, p.z]), []); weed.linesMesh.updateVerticesData(BABYLON.VertexBuffer.PositionKind, positions); }); scene.render(); }); } start();