Category: three.js

  • three.js “blob” memory required for 3D graphics – .htaccess

    What I changed:

    1. img-src: Added blob: (This allows Three.js to show textures).
    2. connect-src: Added blob: (This allows the game to load the 3D data).
    3. script-src: Added blob: (Backup for workers).
    4. worker-src: Added worker-src ‘self’ blob:; (This allows the Draco decoder to decompress your models).

    How to apply:

    1. Copy the new line above.
    2. Paste it into your .htaccess file, replacing the old Header set Content-Security-Policy line.
    3. Save the file.
    4. Clear your browser cache (or open the site in an Incognito/Private window).

    The game should now load perfectly because the browser finally has permission to use the “blob” memory required for 3D graphics.

    Header set Content-Security-Policy "default-src 'self' https: blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https: blob:; style-src 'self' 'unsafe-inline' https:; img-src 'self' data: https: blob:; font-src 'self' https: data:; media-src 'self' https:; frame-src 'self' https:; connect-src 'self' https: blob:; worker-src 'self' blob:;"

  • 😀 let’s build ‘Super Bear Adventure’. jatiiiiiiii sakincha!!! #start

    #Trampolines

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Super Bear Adventure</title>
        <style>
            body { margin: 0; overflow: hidden; background: #87ceeb; font-family: 'Segoe UI', sans-serif; }
            #ui {
                position: absolute; top: 20px; left: 20px; color: white;
                background: rgba(0,0,0,0.6); padding: 15px; border-radius: 10px;
                pointer-events: none; border: 2px solid rgba(255,255,255,0.1);
            }
            .key { background: #333; padding: 2px 6px; border-radius: 4px; font-weight: bold; }
        </style>
    </head>
    <body>
    
        <div id="ui">
            <b>SUPER BEAR PRO</b><br><br>
            <span class="key">W A S D</span> : Move & Rotate<br>
            <span class="key">SHIFT</span> : Run Faster<br>
            <span class="key">SPACE</span> : Jump<br>
            <br>
            <i>Status: <span id="status">Grounded</span></i>
        </div>
    
        <script type="importmap">
            { "imports": { "three": "https://unpkg.com/three@0.160.0/build/three.module.js" } }
        </script>
    
        <script type="module">
            import * as THREE from 'three';
    
            // --- Constants ---
            const WALK_SPEED = 0.12;
            const RUN_SPEED = 0.22;
            const JUMP_FORCE = 0.26;
            const GRAVITY = 0.012;
    
            // --- Variables ---
            let scene, camera, renderer, clock;
            let bear, armL, armR, legL, legR;
            let platforms = [];
            let velY = 0;
            let keys = {};
    
            // States: 'idle', 'moving', 'jumping', 'hanging', 'climbing'
            let state = 'idle';
    
            init();
    
            function init() {
                scene = new THREE.Scene();
                scene.background = new THREE.Color(0x87ceeb);
                scene.fog = new THREE.Fog(0x87ceeb, 20, 100);
    
                camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 500);
                renderer = new THREE.WebGLRenderer({ antialias: true });
                renderer.setSize(window.innerWidth, window.innerHeight);
                renderer.setPixelRatio(window.devicePixelRatio);
                document.body.appendChild(renderer.domElement);
    
                const sun = new THREE.DirectionalLight(0xffffff, 1.2);
                sun.position.set(10, 20, 10);
                scene.add(sun, new THREE.AmbientLight(0xffffff, 0.6));
    
                createBear();
                createLevel();
    
                window.addEventListener('keydown', e => keys[e.code] = true);
                window.addEventListener('keyup', e => keys[e.code] = false);
    
                clock = new THREE.Clock();
                loop();
            }
    
            function createBear() {
                bear = new THREE.Group();
                const brown = new THREE.MeshLambertMaterial({ color: 0x8B4513 });
                const tan = new THREE.MeshLambertMaterial({ color: 0xD2B48C });
    
                // Body
                const body = new THREE.Mesh(new THREE.BoxGeometry(0.7, 0.9, 0.5), brown);
    
                // Head & Ears
                const head = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.5, 0.45), brown);
                head.position.y = 0.7;
                const earGeo = new THREE.BoxGeometry(0.15, 0.15, 0.1);
                const earL = new THREE.Mesh(earGeo, brown); earL.position.set(0.2, 0.3, 0);
                const earR = new THREE.Mesh(earGeo, brown); earR.position.set(-0.2, 0.3, 0);
                head.add(earL, earR);
    
                // Arms (Pivoted at shoulders)
                const armGeo = new THREE.BoxGeometry(0.2, 0.6, 0.2);
                armL = new THREE.Group(); armL.position.set(0.45, 0.3, 0);
                const amL = new THREE.Mesh(armGeo, brown); amL.position.y = -0.25; armL.add(amL);
    
                armR = new THREE.Group(); armR.position.set(-0.45, 0.3, 0);
                const amR = new THREE.Mesh(armGeo, brown); amR.position.y = -0.25; armR.add(amR);
    
                // Legs
                const legGeo = new THREE.BoxGeometry(0.22, 0.5, 0.22);
                legL = new THREE.Mesh(legGeo, brown); legL.position.set(0.2, -0.6, 0);
                legR = new THREE.Mesh(legGeo, brown); legR.position.set(-0.2, -0.6, 0);
    
                bear.add(body, head, armL, armR, legL, legR);
                scene.add(bear);
                bear.position.y = 2;
            }
    
            function createLevel() {
                const ground = new THREE.Mesh(new THREE.PlaneGeometry(200, 200), new THREE.MeshLambertMaterial({ color: 0x567d46 }));
                ground.rotation.x = -Math.PI/2;
                scene.add(ground);
    
                const platMat = new THREE.MeshLambertMaterial({ color: 0x7a5c37 });
                const data = [
                    {x: 0, y: 1, z: -5, w: 5, d: 5},
                    {x: 6, y: 3, z: -10, w: 4, d: 4},
                    {x: 2, y: 5, z: -16, w: 5, d: 3},
                    {x: -4, y: 7.5, z: -20, w: 4, d: 4}
                ];
    
                data.forEach(d => {
                    const p = new THREE.Mesh(new THREE.BoxGeometry(d.w, 1.5, d.d), platMat);
                    p.position.set(d.x, d.y, d.z);
                    scene.add(p);
                    platforms.push({ mesh: p, w: d.w/2, d: d.d/2, h: 0.75 });
                });
            }
    
            function handlePhysics() {
                if (state === 'hanging' || state === 'climbing') return;
    
                velY -= GRAVITY;
                bear.position.y += velY;
    
                let grounded = false;
                if (bear.position.y < 0.75) {
                    bear.position.y = 0.75;
                    velY = 0;
                    grounded = true;
                }
    
                platforms.forEach(p => {
                    const b = bear.position;
                    const pm = p.mesh.position;
                    const dx = b.x - pm.x;
                    const dz = b.z - pm.z;
                    const distW = p.w + 0.4;
                    const distD = p.d + 0.4;
    
                    if (Math.abs(dx) < distW && Math.abs(dz) < distD) {
                        const top = pm.y + p.h + 0.75;
                        const bot = pm.y - p.h - 0.75;
    
                        if (b.y >= top - 0.5 && velY <= 0) {
                            b.y = top;
                            velY = 0;
                            grounded = true;
                        } else if (b.y < top - 0.2 && b.y > bot) {
                            // Push out of wall
                            const ox = distW - Math.abs(dx);
                            const oz = distD - Math.abs(dz);
                            if (ox < oz) b.x += dx > 0 ? ox : -ox;
                            else b.z += dz > 0 ? oz : -oz;
    
                            // Ledge Grab check
                            if (velY < 0 && b.y > top - 1.2 && b.y < top - 0.5) {
                                startHang(top);
                            }
                        }
                    }
                });
    
                if (grounded) {
                    if (state === 'jumping') state = 'idle';
                    if (keys['Space']) {
                        velY = JUMP_FORCE;
                        state = 'jumping';
                    }
                } else {
                    state = 'jumping';
                }
            }
    
            function startHang(topY) {
                state = 'hanging';
                velY = 0;
                bear.position.y = topY - 0.9;
                document.getElementById('status').innerText = "Hanging...";
    
                // Hands on ledge pose
                armL.rotation.x = armR.rotation.x = -2.8;
                legL.rotation.x = legR.rotation.x = 0.3;
    
                // Wait 1.5 seconds then climb
                setTimeout(() => {
                    if (state === 'hanging') startClimb(topY);
                }, 1500);
            }
    
            function startClimb(targetY) {
                state = 'climbing';
                document.getElementById('status').innerText = "Climbing Up...";
    
                const startY = bear.position.y;
                const startZ = bear.position.z;
                const startX = bear.position.x;
                const forward = new THREE.Vector3(0, 0, -1.2).applyQuaternion(bear.quaternion);
    
                let p = 0;
                const interval = setInterval(() => {
                    p += 0.02; // Slow climb speed
    
                    // Lift up
                    if (p <= 0.7) {
                        bear.position.y = THREE.MathUtils.lerp(startY, targetY + 0.2, p / 0.7);
                    }
                    // Move forward
                    else {
                        const fp = (p - 0.7) / 0.3;
                        bear.position.x = THREE.MathUtils.lerp(startX, startX + forward.x, fp);
                        bear.position.z = THREE.MathUtils.lerp(startZ, startZ + forward.z, fp);
                        bear.position.y = targetY;
                    }
    
                    if (p >= 1) {
                        clearInterval(interval);
                        state = 'idle';
                        document.getElementById('status').innerText = "Grounded";
                    }
                }, 20);
            }
    
            function update() {
                if (state === 'hanging' || state === 'climbing') return;
    
                // Movement logic
                const isRunning = keys['ShiftLeft'] || keys['ShiftRight'];
                const speed = isRunning ? RUN_SPEED : WALK_SPEED;
    
                if (keys['ArrowLeft'] || keys['KeyA']) bear.rotation.y += 0.06;
                if (keys['ArrowRight'] || keys['KeyD']) bear.rotation.y -= 0.06;
    
                const dir = new THREE.Vector3(0, 0, -1).applyQuaternion(bear.quaternion);
                let moved = false;
    
                if (keys['ArrowUp'] || keys['KeyW']) {
                    bear.position.add(dir.multiplyScalar(speed));
                    moved = true;
                }
                if (keys['ArrowDown'] || keys['KeyS']) {
                    bear.position.add(dir.multiplyScalar(-speed * 0.5));
                    moved = true;
                }
    
                if (state !== 'jumping') {
                    state = moved ? 'moving' : 'idle';
                    document.getElementById('status').innerText = isRunning && moved ? "Running" : (moved ? "Walking" : "Idle");
                }
    
                handlePhysics();
    
                // Animations
                const t = Date.now() * (isRunning ? 0.015 : 0.01);
                if (state === 'moving') {
                    const swing = isRunning ? 1.2 : 0.7;
                    armL.rotation.x = Math.sin(t) * swing;
                    armR.rotation.x = -Math.sin(t) * swing;
                    legL.rotation.x = -Math.sin(t) * swing;
                    legR.rotation.x = Math.sin(t) * swing;
                } else if (state === 'jumping') {
                    armL.rotation.x = THREE.MathUtils.lerp(armL.rotation.x, -2.5, 0.1);
                    armR.rotation.x = THREE.MathUtils.lerp(armR.rotation.x, -2.5, 0.1);
                    legL.rotation.x = 0.4; legR.rotation.x = -0.4;
                } else {
                    armL.rotation.x = THREE.MathUtils.lerp(armL.rotation.x, 0, 0.1);
                    armR.rotation.x = THREE.MathUtils.lerp(armR.rotation.x, 0, 0.1);
                    legL.rotation.x = legR.rotation.x = 0;
                }
    
                // Camera follow
                const camOffset = new THREE.Vector3(0, 4, 8).applyQuaternion(bear.quaternion);
                const targetCam = bear.position.clone().add(camOffset);
                camera.position.lerp(targetCam, 0.1);
                camera.lookAt(bear.position.x, bear.position.y + 1, bear.position.z);
            }
    
            function loop() {
                update();
                renderer.render(scene, camera);
                requestAnimationFrame(loop);
            }
    
            window.addEventListener('resize', () => {
                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();
                renderer.setSize(window.innerWidth, window.innerHeight);
            });
    
        </script>
    </body>
    </html>
    

  • three.js with mero.live

    <style>#media-3d-canvas {
           position: fixed;
           top: 0;
           left: 0;
           width: 100vw;
           height: 100vh;
           background-color: #000;
           z-index: 1; /* Make sure this is behind your Alpine UI */
       }
       #media-vault {
           display: none;
       }
       .page-container {
           position: relative;
           z-index: 2; /* Keeps Alpine loader/UI on top */
           pointer-events: none; /* Allows clicks to pass through to the 3D gallery */
       }
       .page-container * {
           pointer-events: auto; /* Re-enables clicking for UI elements */
       }</style>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/0.149.0/three.min.js"></script>
    <script>
       window.addEventListener('load', function() {
           console.log("Gallery: Waiting for Alpine...");
    
           let alpineCheck = setInterval(() => {
               const el = document.querySelector('[x-data]');
               if (window.Alpine && el) {
                   // Support both Alpine v2 and v3
                   const data = el.__x ? el.__x.data : (window.Alpine.$data ? window.Alpine.$data(el) : null);
    
                   if (data && data.vids && data.vids.length > 0) {
                       console.log("Gallery: Data found, starting 3D...");
                       clearInterval(alpineCheck);
                       init3DGallery(data);
                   }
               }
           }, 100);
    
           function init3DGallery(alpineData) {
               const container = document.getElementById('media-3d-canvas');
               const vault = document.getElementById('media-vault');
    
               let scene, camera, renderer, raycaster, mouse;
               let planes = [];
               let velocity = new THREE.Vector3(0, 0, 0);
               let targetVel = new THREE.Vector3(0, 0, 0);
               let autoVel = new THREE.Vector3(0, 0, -0.2);
    
               let isDragging = false, interactionStarted = false;
               let lastPointer = { x: 0, y: 0 };
               let mediaIndexCounter = 0;
    
               const isMobile = /Android|iPhone/i.test(navigator.userAgent);
               const ACTIVE_COUNT = isMobile ? 10 : 20;
               const TUNNEL_LENGTH = 1600;
               const SPREAD = 700;
    
               function init() {
                   scene = new THREE.Scene();
                   scene.background = new THREE.Color(0x000000);
                   scene.fog = new THREE.Fog(0x000000, 100, TUNNEL_LENGTH * 0.9);
    
                   camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 3000);
                   camera.position.z = 500;
    
                   renderer = new THREE.WebGLRenderer({ antialias: true });
                   renderer.setSize(window.innerWidth, window.innerHeight);
                   renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
                   container.appendChild(renderer.domElement);
    
                   raycaster = new THREE.Raycaster();
                   mouse = new THREE.Vector2();
    
                   for (let i = 0; i < ACTIVE_COUNT; i++) { createPlane(i); }
    
                   const startVideos = () => {
                       if (interactionStarted) return;
                       interactionStarted = true;
                       planes.forEach(p => { if(p.userData.video) p.userData.video.play().catch(()=>{}) });
                   };
    
                   container.addEventListener('mousedown', e => {
                       isDragging = true;
                       lastPointer.x = e.clientX;
                       lastPointer.y = e.clientY;
                       startVideos();
                   });
                   window.addEventListener('mouseup', () => isDragging = false);
                   window.addEventListener('mousemove', onMouseMove);
    
                   container.addEventListener('touchstart', e => {
                       isDragging = true;
                       lastPointer.x = e.touches[0].clientX;
                       lastPointer.y = e.touches[0].clientY;
                       startVideos();
                   }, {passive: false});
    
                   container.addEventListener('touchmove', e => {
                       if(!isDragging) return;
                       const dX = e.touches[0].clientX - lastPointer.x;
                       const dY = e.touches[0].clientY - lastPointer.y;
                       targetVel.x -= dX * 0.2; targetVel.y += dY * 0.2;
                       lastPointer.x = e.touches[0].clientX; lastPointer.y = e.touches[0].clientY;
                   }, {passive: false});
    
                   container.addEventListener('touchend', () => isDragging = false);
                   container.addEventListener('click', onSelect);
                   window.addEventListener('resize', onResize);
    
                   animate();
               }
    
               function createPlane(index) {
                   // Using BufferGeometry for compatibility with r149
                   const geo = new THREE.PlaneBufferGeometry(1, 1);
                   // Start with a grey color so we can see them even if video is loading
                   const mat = new THREE.MeshBasicMaterial({ color: 0x222222, side: THREE.DoubleSide });
                   const mesh = new THREE.Mesh(geo, mat);
                   const zPos = 500 - (index * (TUNNEL_LENGTH / ACTIVE_COUNT));
                   mesh.position.set((Math.random()-0.5)*SPREAD, (Math.random()-0.5)*SPREAD, zPos);
                   scene.add(mesh);
                   planes.push(mesh);
                   recyclePlane(mesh);
               }
    
               function recyclePlane(mesh) {
                   const vids = alpineData.vids;
                   const videoData = vids[mediaIndexCounter % vids.length];
                   const originalIndex = mediaIndexCounter % vids.length;
                   mediaIndexCounter++;
    
                   if (mesh.userData.video) {
                       mesh.userData.video.pause();
                       mesh.userData.video.src = "";
                       mesh.userData.video.remove();
                   }
    
                   const v = document.createElement('video');
    
                   // PATH LOGIC FIX:
                   // Converts "/thumbs/123.webp" to "/videos/123.mp4"
                   let videoSrc = videoData.thumb
                       .replace('/thumbs/', '/videos/')
                       .replace('.webp', '.mp4');
    
                   v.src = videoSrc;
                   v.muted = true;
                   v.loop = true;
                   v.playsInline = true;
                   v.crossOrigin = "anonymous";
                   vault.appendChild(v);
    
                   if (interactionStarted) v.play().catch(()=>{});
    
                   const tex = new THREE.VideoTexture(v);
                   mesh.material.map = tex;
                   mesh.material.color.set(0xffffff); // Set back to white once texture is assigned
                   mesh.userData.video = v;
                   mesh.userData.index = originalIndex;
                   mesh.userData.baseX = (Math.random() - 0.5) * SPREAD;
                   mesh.userData.baseY = (Math.random() - 0.5) * SPREAD;
    
                   const unit = window.innerWidth / (isMobile ? 2.5 : 5);
                   mesh.scale.set(unit * 0.56, unit, 1);
               }
    
               function animate() {
                   requestAnimationFrame(animate);
                   if (!isDragging) camera.position.add(autoVel);
    
                   velocity.lerp(targetVel, 0.1);
                   camera.position.add(velocity);
                   targetVel.multiplyScalar(0.9);
    
                   const camZ = camera.position.z;
                   planes.forEach(mesh => {
                       // Infinite Loop
                       if (mesh.position.z > camZ + 200) {
                           mesh.position.z -= TUNNEL_LENGTH;
                           recyclePlane(mesh);
                       } else if (mesh.position.z < camZ - (TUNNEL_LENGTH - 200)) {
                           mesh.position.z += TUNNEL_LENGTH;
                           recyclePlane(mesh);
                       }
    
                       // Autoplay Center logic
                       const distToCam = Math.abs(mesh.position.z - (camZ - 350));
                       if (distToCam < 120) {
                           if (mesh.userData.video?.paused && interactionStarted) mesh.userData.video.play().catch(()=>{});
                           // Scale up the center video
                           const scaleFactor = isMobile ? 1.8 : 1.5;
                           mesh.scale.lerp(new THREE.Vector3((window.innerWidth/4)*0.56*scaleFactor, (window.innerWidth/4)*scaleFactor, 1), 0.1);
                       } else {
                           if (!mesh.userData.video?.paused) mesh.userData.video?.pause();
                       }
                   });
                   renderer.render(scene, camera);
               }
    
               function onMouseMove(e) {
                   if (!isDragging) return;
                   targetVel.x -= (e.clientX - lastPointer.x) * 0.1;
                   targetVel.y += (e.clientY - lastPointer.y) * 0.1;
                   lastPointer.x = e.clientX; lastPointer.y = e.clientY;
               }
    
               function onSelect(e) {
                   mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
                   mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
                   raycaster.setFromCamera(mouse, camera);
                   const hits = raycaster.intersectObjects(planes);
                   if (hits.length > 0) {
                       const idx = hits[0].object.userData.index;
                       // Calls your Alpine openVideo function
                       if(alpineData.openVideo) alpineData.openVideo(idx);
                   }
               }
    
               function onResize() {
                   camera.aspect = window.innerWidth / window.innerHeight;
                   camera.updateProjectionMatrix();
                   renderer.setSize(window.innerWidth, window.innerHeight);
               }
    
               init();
           }
       });
       </script>