Script: Touch the grass

Touch the grass picture
Type
Typescript logo indicatortypescript
Date Created
Feb 26, 2025, 9:02:24 AM
Last Edit Date
Mar 5, 2025, 11:02:25 AM

Project Information

This creative coding experience was made by prompting Grok AI LLM

View Full Project

Script 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();
Plans & Pricing

Choose Your Plan

Editor plans for 3D development, API keys for server-side CAD algorithms

B2B

ENTERPRISE

Custom pricing

Custom software development, dedicated servers & CAD automation at scale.

CAD Automation & Software
  • Custom software development
  • Cloud CAD automation pipelines
  • 3D configurators (STEP & GLTF)
  • Batch export jobs
  • Custom algorithms & deployment
Infrastructure & Support
  • Custom compute allocation
  • Dedicated / VPS server tenants
  • Long-running computation jobs
  • Custom upload limits & overage
  • SLA & premium support