~firefoxsimulationsdemo
6 itemsDownload ./*

..
build
jsm
textures
ui
index.html
index.og.html


demoindex.og.html
11 KB• 7•  1 week ago•  DownloadRawClose
1 week ago•  7

{}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Three.js FPS Demo Engine</title>
    <style>
        body { margin: 0; overflow: hidden; font-family: sans-serif; }
        canvas { display: block; }
        #ui {
            position: absolute;
            top: 10px;
            left: 10px;
            color: white;
            background: rgba(0, 0, 0, 0.5);
            padding: 10px;
            pointer-events: none;
            border-radius: 5px;
        }
        #editor-pane {
            position: absolute;
            right: 10px;
            top: 10px;
            width: 200px;
            background: rgba(20, 20, 20, 0.8);
            color: #ccc;
            padding: 15px;
            display: none; /* Toggle this when an object is clicked */
        }
        input { width: 50px; background: #333; color: white; border: 1px solid #555; }
    </style>
</head>
<body>

    <div id="ui">
        <!--<b>Controls:</b> WASD (Move) | Arrows (Turn) | Q/E (Strafe) <br>-->
        <b>Controls:</b> WASD / Arrows (Move) | Q/E (Strafe)<br>
        Edit mode A/D (Turn) | Play mode A/D (Strafe)<br>
        Shift (Sprint) | Space (Jump) | C (Crouch) <br>
        Click Plane to Test Raycast or press G for Play mode
    </div>

    <div id="editor-pane" id="editor">
        <h3>Transform</h3>
        X: <input type="number" id="posX"> <br>
        Y: <input type="number" id="posY"> <br>
        Z: <input type="number" id="posZ">
    </div>

    <script type="importmap">
        {
            "imports": {
                "three": "./build/three.module.js",
                "three/addons/": "./jsm/"
            }
        }
    </script>

    <script type="module">
        import * as THREE from 'three';
        import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

        // --- Core Variables ---
        let scene, camera, renderer, clock;
        let floor, raycaster, mouse;
        
        // --- Player State ---
        const player = {
            height: 1.7,
            velocity: new THREE.Vector3(),
            speed: 5.0,
            rotation: 0,     // Yaw (Left/Right)
            pitch: 0,        // Pitch (Up/Down)
            isGrounded: true,
            isCrouching: false,
            sensitivity: 0.002
        };

        const keys = {};

        let isLocked = false;
        let initialized = false;
        function init() {
            const textureLoader = new THREE.TextureLoader();

            // Load Grass Textures
            const grassDiffuse = textureLoader.load('./textures/grass2.webp');
            const grassNormal = textureLoader.load('./textures/grass2_normal.webp');
            const grassRough = textureLoader.load('./textures/grass2_rough.webp');
            
            // Configure Tiling
            [grassDiffuse, grassNormal, grassRough].forEach(tex => {
                tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
                tex.repeat.set(100, 100); // Adjust based on your scene scale
            });

            // 1. Scene & Camera
            scene = new THREE.Scene();
            scene.background = new THREE.Color(0x87ceeb); // Sky blue
            scene.fog = new THREE.Fog(0x87ceeb, 10, 50);

            camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
            camera.position.set(0, player.height, 5);

            // 2. Renderer
            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setPixelRatio(window.devicePixelRatio);
            renderer.shadowMap.enabled = true;
            renderer.shadowMap.type = THREE.PCFSoftShadowMap;
            document.body.appendChild(renderer.domElement);

            clock = new THREE.Clock();

            // 3. Lighting
            const ambient = new THREE.AmbientLight(0xffffff, 0.4);
            scene.add(ambient);

            const sun = new THREE.DirectionalLight(0xffffff, 1.2);
            sun.position.set(10, 20, 10);
            sun.castShadow = true;
            sun.shadow.camera.left = -20;
            sun.shadow.camera.right = 20;
            sun.shadow.camera.top = 20;
            sun.shadow.camera.bottom = -20;
            scene.add(sun);

            // 4. Floor (The Collider)
            const floorGeo = new THREE.PlaneGeometry(100, 100);
            //const floorMat = new THREE.MeshStandardMaterial({ color: 0x333333, roughness: 0.8 });
            const floorMat = new THREE.MeshStandardMaterial({
                map: grassDiffuse,
                normalMap: grassNormal,
                roughnessMap: grassRough,
                roughness: 0.8
            });
            floor = new THREE.Mesh(floorGeo, floorMat);
            floor.rotation.x = -Math.PI / 2;
            floor.receiveShadow = true;
            floor.name = "Floor";
            scene.add(floor);

            // 5. Input Listeners
            window.addEventListener('keydown', (e) => keys[e.code] = true);
            window.addEventListener('keyup', (e) => keys[e.code] = false);
            
            // 6. Interaction Setup
            raycaster = new THREE.Raycaster();
            mouse = new THREE.Vector2();
            window.addEventListener('click', onMouseClick);

            window.addEventListener('resize', onWindowResize);


            //--// --- Mouse Look Logic ---
            //--document.addEventListener('click', () => {
            //--    // Only lock if we aren't clicking an UI element or already locked
            //--    if(!isLocked) renderer.domElement.requestPointerLock();
            //--});
            //--
            //--document.addEventListener('pointerlockchange', () => {
            //--    isLocked = document.pointerLockElement === renderer.domElement;
            //--});
            window.addEventListener('keydown', (e) => {
                keys[e.code] = true;
            
                // Toggle Play/Edit Mode
                if (e.code === 'KeyG') {
                    if (!isLocked) {
                        renderer.domElement.requestPointerLock();
                    } else {
                        document.exitPointerLock();
                    }
                }
            });

            document.addEventListener('pointerlockchange', () => {
                isLocked = document.pointerLockElement === renderer.domElement;
            
                // Visual feedback (optional)
                const ui = document.getElementById('ui');
                ui.style.border = isLocked ? "2px solid lime" : "2px solid red";
                ui.innerHTML = isLocked ? "<b>MODE: PLAY</b> (G to Edit)" : "<b>MODE: EDIT</b> (G to Play)";
            });

            window.addEventListener('mousemove', (e) => {
                if (!isLocked) return;
            
                // 1. Update Yaw (Left/Right)
                player.rotation -= e.movementX * player.sensitivity;
            
                // 2. Update Pitch (Up/Down)
                player.pitch -= e.movementY * player.sensitivity;
            
                // Clamp pitch to 90 degrees up/down
                player.pitch = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, player.pitch));
            });

            initialized = true;
        }

        function onMouseClick(event) {
            // Calculate mouse position in normalized device coordinates
            mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
            mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

            raycaster.setFromCamera(mouse, camera);
            const intersects = raycaster.intersectObjects(scene.children);

            if (intersects.length > 0) {
                const hit = intersects[0];
                console.log("Hit:", hit.object.name, "at", hit.point);
                // logic for placing or selecting object would go here
            }
        }

        function handleMovement(delta) {
            if(!initialized) return;

            let currentSpeed = player.speed;
            if (keys['ShiftLeft']) currentSpeed *= 1.8;
            
            // 1. Determine Direction Scalars (Cancels out if both keys pressed)
            let moveForward = 0;
            if (keys['KeyW'] || keys['ArrowUp']) moveForward += 1;
            if (keys['KeyS'] || keys['ArrowDown']) moveForward -= 1;
        
            let moveStrafe = 0;
            let turnDir = 0;
            if(isLocked) { // play mode controls
                if (keys['KeyQ']) document.exitPointerLock();;
        
                if (keys['KeyA']) moveStrafe -= 1;
                if (keys['KeyD']) moveStrafe += 1;
                if (keys['ArrowLeft']) turnDir += 1;
                if (keys['ArrowRight']) turnDir -= 1;
            } else { // edit mode controls
                if (keys['KeyE']) moveStrafe += 1;
                if (keys['KeyQ']) moveStrafe -= 1;

                if (keys['KeyA'] || keys['ArrowLeft']) turnDir += 1;
                if (keys['KeyD'] || keys['ArrowRight']) turnDir -= 1;
            }
        
            // 2. Apply Turning
            const turnSpeed = 2.5 * delta;
            player.rotation += turnDir * turnSpeed;
        
            // 3. Calculate Final Movement Vector
            // We clone the vectors so we don't mutate the camera's base orientation
            const forwardVec = new THREE.Vector3(0, 0, -1).applyQuaternion(camera.quaternion);
            forwardVec.y = 0; 
            forwardVec.normalize();
        
            const rightVec = new THREE.Vector3().crossVectors(forwardVec, new THREE.Vector3(0, 1, 0)).normalize();
        
            // Reset horizontal velocity but keep vertical (for gravity/jumping)
            player.velocity.x = 0;
            player.velocity.z = 0;
        
            // Add Forward/Back contribution
            if (moveForward !== 0) {
                player.velocity.add(forwardVec.multiplyScalar(moveForward * currentSpeed));
            }
            // Add Strafe contribution
            if (moveStrafe !== 0) {
                player.velocity.add(rightVec.multiplyScalar(moveStrafe * currentSpeed));
            }
        
            // 4. Update Camera Orientation
            camera.quaternion.setFromEuler(new THREE.Euler(player.pitch, player.rotation, 0, 'YXZ'));
        
            // 5. Physics & Gravity
            if (player.isGrounded && keys['Space']) {
                player.velocity.y = 6.0;
                player.isGrounded = false;
            }
        
            player.velocity.y -= 15.0 * delta;
            camera.position.add(player.velocity.clone().multiplyScalar(delta));
        
            // Floor Snap
            const targetHeight = keys['KeyC'] ? 0.8 : 1.7;
            player.height = THREE.MathUtils.lerp(player.height, targetHeight, 0.1);
        
            if (camera.position.y < player.height) {
                camera.position.y = player.height;
                player.velocity.y = 0;
                player.isGrounded = true;
            }
        }

        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }

        function animate() {
            requestAnimationFrame(animate);
            const delta = clock.getDelta();
            
            handleMovement(delta);
            
            renderer.render(scene, camera);
        }

        init();
        animate();
    </script>
</body>
</html>

Top
©twily.info 2013 - 2026
twily at twily dot info



2 741 826 visits
... ^ v