1

I have made this game and when I look one direction, the camera controls are normal, but if I look behind me then they inverse and to the side it makes my camera roll. I suspect this is because the camera in only relative to looking the initial direction, but I cant fix it. Can somebody help? For some reason it displays an error on stack overflow, but it works normal in another editor. Here is my code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>First Person Game with Four Cameras</title>
    <style>
        body { margin: 0; }
        canvas { display: block; }
        #minimap {
            position: absolute;
            top: 10px;
            left: 10px;
            width: 200px;
            height: 200px;
            border: 1px solid #000;
            background-color: rgba(255, 255, 255, 0.5);
            pointer-events: none;
        }
        #controls {
            position: absolute;
            top: 10px;
            right: 10px;
            background: rgba(255, 255, 255, 0.8);
            padding: 10px;
            border-radius: 5px;
        }
        #controls label {
            display: block;
            margin: 10px 0;
        }
        input[type=range] {
            width: 100%;
            height: 10px;
            -webkit-appearance: none;
            appearance: none;
            background: #ddd;
            border-radius: 5px;
        }
        input[type=range]::-webkit-slider-thumb {
            -webkit-appearance: none;
            appearance: none;
            width: 20px;
            height: 20px;
            background: #4CAF50;
            border-radius: 50%;
            cursor: pointer;
        }
        input[type=range]::-moz-range-thumb {
            width: 20px;
            height: 20px;
            background: #4CAF50;
            border: none;
            border-radius: 50%;
            cursor: pointer;
        }
    </style>
</head>
<body>
<div id="minimap"></div>
<div id="controls">
    <label for="fov">Field of View: <input type="range" id="fov" min="30" max="100" value="75"></label>
    <label for="brightness">Brightness: <input type="range" id="brightness" min="0" max="2" step="0.1" value="1"></label>
    <button id="pause">Pause</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
    let scene, renderer;
    let moveForward = false, moveBackward = false, moveLeft = false, moveRight = false;
    let velocity = new THREE.Vector3();
    let direction = new THREE.Vector3();
    let canJump = false;
    let prevTime = performance.now();
    let cubes = [], cubeVelocities = [];
    let heldCube = null;
    let aiBlock;

    const speed = 150.0;
    const jumpVelocity = 30.0;
    const gravity = 9.8 * 5.0;
    const pickUpDistance = 2.0;
    const originalCubeScale = 1;
    const heldCubeScale = 0.5;
    const friction = 0.05;
    let pitch = 0, yaw = 0;

    // Day/Night cycle variables
    let dayTime = 0;
    const dayDuration = 10000;
    let isPaused = false;

    // Define four cameras for North, South, East, West
    const cameras = {
        north: new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000),
        south: new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000),
        east: new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000),
        west: new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000),
    };

    // Initial camera positions and rotations
    cameras.north.position.set(0, 1.6, -5);
    cameras.south.position.set(0, 1.6, 5);
    cameras.east.position.set(5, 1.6, 0);
    cameras.west.position.set(-5, 1.6, 0);

    cameras.north.rotation.set(0, 0, 0);
    cameras.south.rotation.set(0, Math.PI, 0);
    cameras.east.rotation.set(0, Math.PI / 2, 0);
    cameras.west.rotation.set(0, -Math.PI / 2, 0);

    // Starting camera direction
    let currentCamera = cameras.north;

    init();
    animate();

    function init() {
        scene = new THREE.Scene();
        renderer = new THREE.WebGLRenderer();
        renderer.setSize(window.innerWidth, window.innerHeight);
        document.body.appendChild(renderer.domElement);

        const groundGeometry = new THREE.PlaneGeometry(50, 50);
        const groundMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
        const ground = new THREE.Mesh(groundGeometry, groundMaterial);
        ground.rotation.x = -Math.PI / 2;
        scene.add(ground);

        const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
        const cubeMaterial = new THREE.MeshBasicMaterial({ color: 0x0000ff });
        for (let i = 0; i < 3; i++) {
            const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
            cube.position.set(Math.random() * 30 - 15, 1.6, Math.random() * 30 - 15);
            cubes.push(cube);
            cubeVelocities.push(new THREE.Vector3());
            scene.add(cube);
        }

        const aiMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
        aiBlock = new THREE.Mesh(cubeGeometry, aiMaterial);
        aiBlock.position.set(Math.random() * 30 - 15, 1.6, Math.random() * 30 - 15);
        scene.add(aiBlock);

        document.addEventListener('keydown', onKeyDown);
        document.addEventListener('keyup', onKeyUp);
        document.getElementById('pause').addEventListener('click', togglePause);

        document.getElementById('fov').addEventListener('input', (event) => {
            for (let cam in cameras) {
                cameras[cam].fov = event.target.value;
                cameras[cam].updateProjectionMatrix();
            }
        });
        document.getElementById('brightness').addEventListener('input', (event) => {
            scene.background = new THREE.Color(`hsl(0, 0%, ${event.target.value * 100}%)`);
        });

        document.body.addEventListener('click', () => {
            document.body.requestPointerLock();
        });
        document.addEventListener('mousemove', onMouseMove);

        window.addEventListener('resize', onWindowResize);
    }

    function togglePause() {
        isPaused = !isPaused;
        const controls = document.getElementById('controls');
        if (isPaused) {
            controls.classList.add('paused');
        } else {
            controls.classList.remove('paused');
        }
    }

    function onWindowResize() {
        for (let cam in cameras) {
            cameras[cam].aspect = window.innerWidth / window.innerHeight;
            cameras[cam].updateProjectionMatrix();
        }
        renderer.setSize(window.innerWidth, window.innerHeight);
    }

    function onKeyDown(event) {
        switch (event.code) {
            case 'KeyW': moveForward = true; break;
            case 'KeyS': moveBackward = true; break;
            case 'KeyA': moveLeft = true; break; 
            case 'KeyD': moveRight = true; break;
            case 'Space':
                if (canJump) {
                    velocity.y = jumpVelocity;
                    canJump = false;
                }
                break;
            case 'KeyF':
                pickOrThrowCube();
                break;
            case 'KeyP':
                togglePause();
                break;
            case 'Digit1':
                currentCamera = cameras.north; // Switch to North camera
                break;
            case 'Digit2':
                currentCamera = cameras.south; // Switch to South camera
                break;
            case 'Digit3':
                currentCamera = cameras.east; // Switch to East camera
                break;
            case 'Digit4':
                currentCamera = cameras.west; // Switch to West camera
                break;
        }
    }

    function onKeyUp(event) {
        switch (event.code) {
            case 'KeyW': moveForward = false; break;
            case 'KeyS': moveBackward = false; break;
            case 'KeyA': moveLeft = false; break; 
            case 'KeyD': moveRight = false; break;
        }
    }

    function onMouseMove(event) {
        if (document.pointerLockElement) {
            const sensitivity = 0.002;

            yaw -= event.movementX * sensitivity;
            pitch -= event.movementY * sensitivity;
            pitch = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, pitch));

            // Apply rotation to the current camera
            currentCamera.rotation.y = yaw;
            currentCamera.rotation.x = pitch;
            currentCamera.rotation.z = 0;
        }
    }

    function pickOrThrowCube() {
        if (heldCube) {
            const throwVelocity = new THREE.Vector3();
            currentCamera.getWorldDirection(throwVelocity);
            throwVelocity.multiplyScalar(300);
            heldCube.position.add(throwVelocity.multiplyScalar(0.04));
            cubeVelocities[cubes.indexOf(heldCube)].copy(throwVelocity);
            heldCube.scale.set(originalCubeScale, originalCubeScale, originalCubeScale);
            heldCube = null;
        } else {
            const raycaster = new THREE.Raycaster();
            raycaster.setFromCamera(new THREE.Vector2(0, 0), currentCamera);
            const intersects = raycaster.intersectObjects(cubes.concat(aiBlock));

            if (intersects.length > 0) {
                const intersect = intersects[0];
                if (intersect.distance < pickUpDistance) {
                    heldCube = intersect.object;
                    cubeVelocities[cubes.indexOf(heldCube)] = new THREE.Vector3();
                    heldCube.scale.set(heldCubeScale, heldCubeScale, heldCubeScale);
                    heldCube.position.copy(currentCamera.position).add(currentCamera.getWorldDirection(new THREE.Vector3()).multiplyScalar(1.5));
                }
            }
        }
    }

    function animate() {
        if (!isPaused) {
            requestAnimationFrame(animate);

            const time = performance.now();
            const delta = (time - prevTime) / 1000;

            dayTime += delta;
            if (dayTime > dayDuration) dayTime = 0;

            const brightness = (Math.sin((dayTime / dayDuration) * Math.PI) + 1) / 2;
            scene.background = new THREE.Color(`hsl(200, 100%, ${brightness * 100}%)`);

            direction.z = Number(moveForward) - Number(moveBackward);
            direction.x = Number(moveRight) - Number(moveLeft);
            direction.normalize();

            velocity.x -= velocity.x * 10.0 * delta;
            velocity.z -= velocity.z * 10.0 * delta;
            velocity.y -= gravity * delta;

            if (moveForward || moveBackward || moveLeft || moveRight) {
                const frontDirection = new THREE.Vector3();
                currentCamera.getWorldDirection(frontDirection);
                frontDirection.y = 0;
                frontDirection.normalize();

                const rightDirection = new THREE.Vector3();
                rightDirection.crossVectors(currentCamera.up, frontDirection).normalize();

                frontDirection.multiplyScalar(direction.z * speed * delta);
                rightDirection.multiplyScalar(direction.x * speed * delta);

                velocity.add(frontDirection).add(rightDirection);
            }

            currentCamera.position.addScaledVector(velocity, delta);

            currentCamera.position.x = Math.max(-24, Math.min(24, currentCamera.position.x));
            currentCamera.position.z = Math.max(-24, Math.min(24, currentCamera.position.z));

            if (currentCamera.position.y < 1.6) {
                velocity.y = 0;
                currentCamera.position.y = 1.6;
                canJump = true;
            }

            if (heldCube) {
                heldCube.position.copy(currentCamera.position).add(currentCamera.getWorldDirection(new THREE.Vector3()).multiplyScalar(1.5));
            }

            for (let i = 0; i < cubes.length; i++) {
                if (cubes[i] !== heldCube) {
                    cubeVelocities[i].x *= (1 - friction);
                    cubeVelocities[i].z *= (1 - friction);
                    cubeVelocities[i].y -= gravity * delta;
                    cubes[i].position.addScaledVector(cubeVelocities[i], delta);

                    if (cubes[i].position.y < 0.5) {
                        cubes[i].position.y = 0.5;
                        cubeVelocities[i].y = Math.abs(cubeVelocities[i].y) * 0.2;
                    }

                    for (let j = 0; j < cubes.length; j++) {
                        if (i !== j) {
                            const distance = cubes[i].position.distanceTo(cubes[j].position);
                            if (distance < 1.5) {
                                const collisionDirection = new THREE.Vector3().subVectors(cubes[i].position, cubes[j].position).normalize();
                                const relativeVelocity = new THREE.Vector3().subVectors(cubeVelocities[i], cubeVelocities[j]);
                                if (relativeVelocity.dot(collisionDirection) < 0) {
                                    const pushAmount = 0.1;
                                    cubeVelocities[i].add(collisionDirection.clone().multiplyScalar(pushAmount));
                                    cubeVelocities[j].sub(collisionDirection.clone().multiplyScalar(pushAmount));
                                }
                            }
                        }
                    }
                }
            }

            aiBlock.position.y -= gravity * delta;
            aiBlock.position.x += (Math.random() - 0.5) * 0.05;
            aiBlock.position.z += (Math.random() - 0.5) * 0.05;

            aiBlock.position.x = Math.max(-24, Math.min(24, aiBlock.position.x));
            aiBlock.position.z = Math.max(-24, Math.min(24, aiBlock.position.z));
            if (aiBlock.position.y < 1.6) {
                aiBlock.position.y = 1.6;
            }

            const playerBox = new THREE.Box3().setFromCenterAndSize(currentCamera.position, new THREE.Vector3(1, 1, 1));
            const aiBox = new THREE.Box3().setFromCenterAndSize(aiBlock.position, new THREE.Vector3(1, 1, 1));
            if (playerBox.intersectsBox(aiBox)) {
                const collisionDirection = new THREE.Vector3().subVectors(currentCamera.position, aiBlock.position).normalize();
                velocity.add(collisionDirection.multiplyScalar(10));
                aiBlock.position.add(collisionDirection.multiplyScalar(7));
            }

            renderer.render(scene, currentCamera);
            prevTime = time;

            updateMiniMap();
        }
    }

    function updateMiniMap() {
        const minimap = document.getElementById('minimap');
        const ctx = minimap.getContext('2d');
        ctx.clearRect(0, 0, minimap.width, minimap.height);

        ctx.fillStyle = 'lightgreen';
        ctx.fillRect(0, 0, minimap.width, minimap.height);

        ctx.fillStyle = 'blue';
        const playerX = (currentCamera.position.x + 25) * 4;
        const playerZ = (currentCamera.position.z + 25) * 4;
        ctx.fillRect(playerX, playerZ, 5, 5);

        ctx.fillStyle = 'blue';
        cubes.forEach(cube => {
            const cubeX = (cube.position.x + 25) * 4;
            const cubeZ = (cube.position.z + 25) * 4;
            ctx.fillRect(cubeX, cubeZ, 5, 5);
        });

        ctx.fillStyle = 'red';
        const aiX = (aiBlock.position.x + 25) * 4;
        const aiZ = (aiBlock.position.z + 25) * 4;
        ctx.fillRect(aiX, aiZ, 5, 5);
    }
</script>
</body>
</html>

1
  • the problem is same that you asked in question before and i answered it. It is problem with rotation matrix multiplication order Commented Feb 25 at 11:42

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.