make player always hold cube from the center of the cube : class World {
    constructor() {
        this.game = new Game()
        this.scene = this.game.scene
        this.physics = this.initPhysics()
        this.physics.gravity.set(0,-10,0)
        this.defaultMaterial = new CANNON.Material('default')
        this.raycaster = new THREE.Raycaster();
        this.mouse = new THREE.Vector2();
        this.leftPortal = []
        this.rightPortal = []
        this.portalPhysics = false
        this.collisionDetected = false;
        this.virtualCamera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        this.scene.add(this.virtualCamera);
        this.collisionDetected = false
        this.portalHandler = new Portal(this.scene, this.game.renderer.instance);
        this.active = ''
        this.previousContacts = [];
        this.worldBounds = {
            min: new CANNON.Vec3(-100, -100, -100),
            max: new CANNON.Vec3(100, 100, 100)
        }
        const defaultContactMaterial = new CANNON.ContactMaterial(
            this.defaultMaterial,
            this.defaultMaterial,
            {
                friction: 0,
                restitution: 0
            }
        )
        this.physics.defaultContactMaterial = defaultContactMaterial
        window.addEventListener('click', this.shoot.bind(this));

        this.isHoldingCube = false;
        this.holdDistance = 10;
        this.pickupRange = 20;

        this.holdingConstraint = null;
        this.holdBody = null;

        this.onKeyDown = this.onKeyDown.bind(this);
        window.addEventListener('keydown', this.onKeyDown);

        this.setWorld()
        this.createCompanionCube();
        this.loadTextures()
    }

    initPhysics(){
        let world = new CANNON.World();
        world.quatNormalizeSkip = 0;
        world.quatNormalizeFast = false;
    
        var solver = new CANNON.GSSolver();
    
        world.defaultContactMaterial.contactEquationStiffness = 1e9;
        world.defaultContactMaterial.contactEquationRelaxation = 4;
    
        solver.iterations = 10;
        solver.tolerance = 0.1;
        let split = true;
        if(split)
            world.solver = new CANNON.SplitSolver(solver);
        else
            world.solver = solver;
    
        world.gravity.set(0,-9.8,0);
        world.broadphase = new CANNON.NaiveBroadphase();
    
        return world
    }

    worldToLocal(object, vector) {
        const worldInverse = new THREE.Matrix4().copy(object.matrixWorld).invert();
        return vector.clone().applyMatrix4(worldInverse);
    }
    
    localToWorld(object, vector) {
        return vector.clone().applyMatrix4(object.matrixWorld);
    }

    shoot(event) {
        this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
        this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

        this.raycaster.setFromCamera({x:0,y:0}, this.game.camera.instance);

        const intersects = this.raycaster.intersectObjects([
            this.plane, this.roof, this.scene.getObjectByName('backWall'),
            this.scene.getObjectByName('frontWall'), this.scene.getObjectByName('leftWall'),
            this.scene.getObjectByName('rightWall')
        ]);

        if (intersects.length > 0) {
            const intersection = intersects[0];
            const object = intersection.object;

            const geometry = new THREE.CircleGeometry(5, 32);
            const material = new THREE.MeshBasicMaterial({ color: 0x000000 });
            const circle = new THREE.Mesh(geometry, material);

            const portalSize = new THREE.Vector2(10, 15);
            const halfPortalSize = portalSize.clone().multiplyScalar(0.5);
            const wallSize = new THREE.Vector2(object.geometry.parameters.width, object.geometry.parameters.height);
            const halfWallSize = wallSize.clone().multiplyScalar(0.5);

            let localPosition = this.worldToLocal(object, intersection.point);

            localPosition.x = THREE.MathUtils.clamp(localPosition.x, -halfWallSize.x + halfPortalSize.x, halfWallSize.x - halfPortalSize.x);
            localPosition.y = THREE.MathUtils.clamp(localPosition.y, -halfWallSize.y + halfPortalSize.y, halfWallSize.y - halfPortalSize.y);

            const validPoint = this.localToWorld(object, localPosition);

            circle.position.copy(validPoint);
            circle.rotation.copy(object.rotation);
            circle.scale.y = 1.5;

            circle.position.addScaledVector(circle.getWorldDirection(new THREE.Vector3()), 0.01);
            this.boxBody = new CANNON.Body({
                collisionFilterGroup: GROUP_PORTAL,
                collisionFilterMask: GROUP_WALL | GROUP_DEFAULT | GROUP_CUBE | GROUP_PLAYER,
            })
            this.boxBody.mass = 0
            this.boxBody.material = this.defaultMaterial
            this.boxBody.collisionResponse = false
            this.boxBody.addShape(new CANNON.Box(new CANNON.Vec3(3.34,5,1.5)))
            this.boxBody.quaternion.copy(circle.quaternion)
            this.boxBody.position.copy(circle.position)
            this.boxBody.class = 'portal'
            this.boxBody.object = intersects[0].object

            let intersectionDetected = false;
            if (event.button === 0) {
                this.rightPortal.forEach((rightPortal) => {
                    if (this.boxBody.object === rightPortal.physicObject.object && this.detectIntersection(this.boxBody, rightPortal.physicObject)) {
                        intersectionDetected = true;
                    }
                });
            } else if (event.button === 2) {
                this.leftPortal.forEach((leftPortal) => {
                    if (this.boxBody.object === leftPortal.physicObject.object && this.detectIntersection(this.boxBody, leftPortal.physicObject)) {
                        intersectionDetected = true;
                    }
                });
            }

            if (intersectionDetected) {
                return;
            }

            this.physics.addBody(this.boxBody)
            circle.physicObject = this.boxBody

            const geometry1 = new THREE.PlaneGeometry(19, 19);
            let materialPortal;

            if (event.button === 0) {
                this.materialBlue = new THREE.ShaderMaterial({
                    uniforms: {
                        iResolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) },
                        iTime: { value: 0 }
                    },
                    vertexShader: document.getElementById('vertexShader').textContent,
                    fragmentShader: document.getElementById('fragmentShaderOrange').textContent
                });
                materialPortal = this.materialBlue;
                this.boxBody.ref = 'left'
                this.leftPortal.push(circle);
            } else if (event.button === 2) {
                this.materialOrange = new THREE.ShaderMaterial({
                    uniforms: {
                        iResolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) },
                        iTime: { value: 0 }
                    },
                    vertexShader: document.getElementById('vertexShader').textContent,
                    fragmentShader: document.getElementById('fragmentShaderBlue').textContent
                });
                materialPortal = this.materialOrange;
                this.boxBody.ref = 'right'
                this.rightPortal.push(circle);
            }

            const planePortal = new THREE.Mesh(geometry1, materialPortal);
            planePortal.position.copy(circle.position);
            planePortal.rotation.copy(circle.rotation);
            planePortal.position.addScaledVector(circle.getWorldDirection(new THREE.Vector3()), 0.01);

            this.scene.add(planePortal);
            circle.torus = planePortal;
        }
    }

    detectIntersection(bodyA, bodyB, buffer = 4) {
        const shapeA = bodyA.shapes[0];
        const shapeB = bodyB.shapes[0];
    
        const posA = bodyA.position;
        const posB = bodyB.position;
    
        const halfSizeA = shapeA.halfExtents;
        const halfSizeB = shapeB.halfExtents;
    
        return (
            Math.abs(posA.x - posB.x) < (halfSizeA.x + halfSizeB.x + buffer) &&
            Math.abs(posA.y - posB.y) < (halfSizeA.y + halfSizeB.y + buffer) &&
            Math.abs(posA.z - posB.z) < (halfSizeA.z + halfSizeB.z + buffer)
        );
    }    

    calculateAngle(v1, v2) {
        const dot = v1.dot(v2);
        const angle = Math.acos(dot / (v1.length() * v2.length()));
        return angle;
    }

    isBodyOutOfBounds(body) {
        const position = body.position;
        return (
          position.x < this.worldBounds.min.x ||
          position.x > this.worldBounds.max.x ||
          position.y < this.worldBounds.min.y ||
          position.y > this.worldBounds.max.y ||
          position.z < this.worldBounds.min.z ||
          position.z > this.worldBounds.max.z
        );
      }

      setWorld() {
        const floorShape = new CANNON.Plane();
        const floorBody = new CANNON.Body({
            collisionFilterGroup: GROUP_WALL,
            collisionFilterMask: GROUP_PLAYER | GROUP_CUBE | GROUP_PORTAL | GROUP_DEFAULT,
        });
        floorBody.mass = 0;
        floorBody.addShape(floorShape);
        floorBody.quaternion.setFromAxisAngle(new CANNON.Vec3(-1, 0, 0), Math.PI * 0.5);
        this.physics.addBody(floorBody);
    
        const floorTexture = new THREE.TextureLoader().load('/textures/concrete_modular_floor001c.png');
        floorTexture.repeat.set(4, 4);
        floorTexture.wrapS = THREE.RepeatWrapping;
        floorTexture.wrapT = THREE.RepeatWrapping;
    
        const floorMaterial = new THREE.MeshStandardMaterial({
            map: floorTexture,
            roughness: 0.2,
            metalness: 0.1
        });
    
        this.plane = new THREE.Mesh(
            new THREE.PlaneGeometry(200, 200),
            floorMaterial
        );
        this.plane.rotation.x = -Math.PI / 2;
        this.plane.receiveShadow = true;
        this.plane.position.set(0, 0, 0);
        this.scene.add(this.plane);
        this.plane.physicObject = floorBody;
    
        const wallShape = new CANNON.Box(new CANNON.Vec3(100, 50, 1));
    
        const createWall = (width, height, depth, color, position, rotation) => {
            const wallTexture = new THREE.TextureLoader().load('/textures/concrete_modular_wall001a.png');
            wallTexture.repeat.set(4, 3);
            wallTexture.wrapS = THREE.RepeatWrapping;
            wallTexture.wrapT = THREE.RepeatWrapping;
    
            const wallMaterial = new THREE.MeshStandardMaterial({
                map: wallTexture,
                side: THREE.DoubleSide
            });
    
            const wallMesh = new THREE.Mesh(
                new THREE.PlaneGeometry(width, height),
                wallMaterial
            );
            wallMesh.position.set(position.x, position.y, position.z);
            wallMesh.rotation.set(rotation.x, rotation.y, rotation.z);
            this.scene.add(wallMesh);
    
            const wallBody = new CANNON.Body({
                collisionFilterGroup: GROUP_WALL,
                collisionFilterMask: GROUP_PLAYER | GROUP_CUBE | GROUP_PORTAL | GROUP_DEFAULT,
            });
            wallBody.mass = 0;
            wallBody.addShape(wallShape);
            wallBody.position.copy(wallMesh.position);
            wallBody.quaternion.copy(wallMesh.quaternion);
            this.physics.addBody(wallBody);
    
            wallMesh.physicObject = wallBody;
            return wallMesh;
        };
    
        const wallHeight = 100;
        const wallYPosition = wallHeight / 2;
    
        let backWallMesh = createWall(200, wallHeight, 1, 0xf0ffff, { x: 0, y: wallYPosition, z: 100 }, { x: 0, y: Math.PI, z: 0 });
        backWallMesh.name = 'backWall';
    
        let frontWallMesh = createWall(200, wallHeight, 1, 0xff0fff, { x: 0, y: wallYPosition, z: -100 }, { x: 0, y: 0, z: 0 });
        frontWallMesh.name = 'frontWall';
    
        let leftWallMesh = createWall(200, wallHeight, 1, 0xfff0ff, { x: -100, y: wallYPosition, z: 0 }, { x: 0, y: Math.PI / 2, z: 0 });
        leftWallMesh.name = 'leftWall';
    
        let rightWallMesh = createWall(200, wallHeight, 1, 0xffff0f, { x: 100, y: wallYPosition, z: 0 }, { x: 0, y: -Math.PI / 2, z: 0 });
        rightWallMesh.name = 'rightWall';
    
        const roofTexture = new THREE.TextureLoader().load('/textures/concrete_modular_ceiling001a.png');
        roofTexture.repeat.set(4, 4);
        roofTexture.wrapS = THREE.RepeatWrapping;
        roofTexture.wrapT = THREE.RepeatWrapping;
    
        const roofMaterial = new THREE.MeshStandardMaterial({
            map: roofTexture,
        });
    
        this.roof = new THREE.Mesh(
            new THREE.PlaneGeometry(200, 200),
            roofMaterial
        );
        this.roof.rotation.x = Math.PI / 2;
        this.roof.position.y = wallHeight;
        this.scene.add(this.roof);
    
        const roofShape = new CANNON.Plane();
        const roofBody = new CANNON.Body({
            collisionFilterGroup: GROUP_WALL,
            collisionFilterMask: GROUP_PLAYER | GROUP_CUBE | GROUP_PORTAL | GROUP_DEFAULT,
        });
        roofBody.mass = 0;
        roofBody.addShape(roofShape);
        roofBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), Math.PI * 0.5);
        roofBody.position.y = wallHeight;
        this.physics.addBody(roofBody);
    
        this.roof.physicObject = roofBody;
    }    
    
    loadTextures() {
        this.game.resources.on('ready', () => {
            
        })
    }

    renderPortal(){
        this.game.renderer.instance.clear();
        this.game.camera.instance.updateMatrixWorld(true);
        this.portalHandler.render(this.game.camera.instance, 0, this.virtualCamera, [this.rightPortal[0], this.leftPortal[0]],this.game.camera.instance.matrixWorld.clone(),this.game.camera.instance.projectionMatrix.clone());
    }

    isWithinFunnel(coneOrigin, coneDirection, objectPosition, coneAngle, coneHeight = 10) {
        const upDirection = new THREE.Vector3(0, 1, 0);
        const facingUpward = coneDirection.dot(upDirection) > 0.7;
    
        if (!facingUpward) {
            return false;
        }
    
        const toObject = new THREE.Vector3().subVectors(objectPosition, coneOrigin);
        
        const projectedDistance = toObject.dot(coneDirection);
        
        if (projectedDistance > coneHeight || projectedDistance < 0) {
            return false;
        }
        
        const directionToObject = toObject.normalize();
        const angle = coneDirection.angleTo(directionToObject);
        
        return angle < coneAngle;
    }

    funnelObjectTowardsPortal(objectPosition, coneOrigin) {
        const toConeOrigin = new THREE.Vector3().copy(coneOrigin).sub(objectPosition);
        toConeOrigin.y = 0;
        
        const direction = toConeOrigin.normalize();
        
        const funnelSpeed = 0.2;
        
        const newPosition = new THREE.Vector3().copy(objectPosition);
        newPosition.x += direction.x * funnelSpeed;
        newPosition.z += direction.z * funnelSpeed;
        
        this.game.controls.cameraBody.position.set(newPosition.x, this.game.controls.cameraBody.position.y, newPosition.z);
    }       
    
    update() {
        this.physics.step(1 / 75, this.game.time.delta, 3)

        this.contact = false

        if(this.rightPortal.length > 0){
            document.querySelector('.blue').classList.add('active')
        }
        else{
            document.querySelector('.blue').classList.remove('active')
        }

        if(this.leftPortal.length > 0){
            document.querySelector('.orange').classList.add('active')
        }
        else{
            document.querySelector('.orange').classList.remove('active')
        }

        if (this.rightPortal.length > 0 && this.leftPortal.length > 0) {
            const funnelConeAngle = Math.PI / 6;
            const playerPosition = this.game.camera.instance.position;

            this.leftPortal.forEach((portal) => {
                const coneOrigin = portal.position;
                const coneDirection = new THREE.Vector3();
                portal.getWorldDirection(coneDirection);

                if (this.isWithinFunnel(coneOrigin, coneDirection, playerPosition, funnelConeAngle)) {
                    this.funnelObjectTowardsPortal(playerPosition, coneOrigin);
                }
            });

            this.rightPortal.forEach((portal) => {
                const coneOrigin = portal.position;
                const coneDirection = new THREE.Vector3();
                portal.getWorldDirection(coneDirection);

                if (this.isWithinFunnel(coneOrigin, coneDirection, playerPosition, funnelConeAngle)) {
                    this.funnelObjectTowardsPortal(playerPosition, coneOrigin);
                }
            });
        }

        this.physics.contacts.forEach((contact) => {
            let bodyA = contact.bi
            let bodyB = contact.bj
            if(((bodyA.class == 'camera' && bodyB.class == 'portal') || (bodyB.class == 'camera' && bodyA.class == 'portal')) && (this.rightPortal.length > 0 && this.leftPortal.length > 0)){
                this.contact = true
            }
        })

        if(this.materialBlue){
            this.materialBlue.uniforms.iTime.value += 0.05;
        }
        if(this.materialOrange){
            this.materialOrange.uniforms.iTime.value += 0.05;
        }

        if (this.isBodyOutOfBounds(this.game.controls.cameraBody)) {
            this.game.controls.cameraBody.sleep()
            this.game.controls.cameraBody.position.set(20, 10, 0)
            this.game.controls.cameraBody.wakeUp()
        }

        if (this.rightPortal.length > 0 && this.leftPortal.length > 0){
            this.rightPortal[0].pair = this.leftPortal[0]
            this.leftPortal[0].pair = this.rightPortal[0]
            this.game.camera.instance.layers.set(0)
            this.renderPortal()
            this.game.renderer.instance.clearDepth()
            this.game.camera.instance.layers.set(1)
            this.game.renderer.instance.render(this.scene, this.game.camera.instance)
        }
        else{
            this.tmpScene = new THREE.Scene()
            this.tmpScene.children = this.scene.children.filter((child)=>child.class!=='player')
            this.game.camera.instance.layers.set(0)
            this.game.renderer.instance.render(this.tmpScene, this.game.camera.instance)
            this.game.renderer.instance.clearDepth()
            this.game.camera.instance.layers.set(1)
            this.game.renderer.instance.render(this.tmpScene, this.game.camera.instance)
        }

        this.managePortals()

        if (this.rightPortal.length > 0 && this.leftPortal.length > 0) {
            this.checkCubePortalTeleport();
            
            let cubePortalContact = false;
            let playerPortalContact = false;
        
            for (let i = 0; i < this.physics.contacts.length; i++) {
                const contact = this.physics.contacts[i];
                const bodyA = contact.bi;
                const bodyB = contact.bj;
                
                if ((bodyA.class === 'companionCube' && bodyB.class === 'portal') || 
                    (bodyA.class === 'portal' && bodyB.class === 'companionCube')) {
                    this.companionCubeBody.collisionFilterMask &= ~GROUP_WALL;
                    cubePortalContact = true;
                }

                if ((bodyA.class === 'camera' && bodyB.class === 'portal') || 
                    (bodyA.class === 'portal' && bodyB.class === 'camera')) {
                    this.game.controls.cameraBody.collisionFilterMask &= ~GROUP_WALL;
                    playerPortalContact = true;
                }
        
                if ((bodyA.class === 'camera' && bodyB.ref === 'right') || 
                    (bodyA.ref === 'right' && bodyB.class === 'camera')) {
                    this.checkLeftPortalTeleport();
                    break;
                }
            }
        
            if (!cubePortalContact) {
                this.companionCubeBody.collisionFilterMask = GROUP_DEFAULT | GROUP_WALL | GROUP_PORTAL | GROUP_PLAYER

            }

            if(!playerPortalContact){
                this.game.controls.cameraBody.collisionFilterMask = GROUP_DEFAULT | GROUP_WALL | GROUP_PORTAL | GROUP_CUBE
            }

            for (let i = 0; i < this.physics.contacts.length; i++) {
                const contact = this.physics.contacts[i];
                const bodyA = contact.bi;
                const bodyB = contact.bj;

                if ((bodyA.class === 'camera' && bodyB.ref === 'left') || (bodyA.ref === 'left' && bodyB.class === 'camera')) {
                    this.checkRightPortalTeleport()
                    break;
                }
            }
        } else {
            this.resetPortalColors()
        }

        if (!this.collisionDetected) {
            this.game.controls.cameraBody.wakeUp();
        }

        if (this.holdBody) {
            const holdPosition = this.getHoldPosition();
            this.holdBody.position.copy(holdPosition);
            this.holdBody.quaternion.copy(this.game.camera.instance.quaternion);
        }

        if (this.companionCube) {
            this.companionCube.position.copy(this.companionCubeBody.position);
            this.companionCube.quaternion.copy(this.companionCubeBody.quaternion);
        }
    }

    managePortals() {
        this.leftPortal.forEach((portalData) => {
            this.scene.add(portalData)
        })

        if (this.leftPortal.length > 1) {
            const circleToRemove = this.leftPortal.shift()
            this.scene.remove(circleToRemove.box)
            this.scene.remove(circleToRemove.torus)
            this.physics.remove(circleToRemove.physicObject)
            this.scene.remove(circleToRemove)
        }

        this.rightPortal.forEach((portalData) => {
            this.scene.add(portalData)
        })

        if (this.rightPortal.length > 1) {
            const circleToRemove = this.rightPortal.shift()
            this.scene.remove(circleToRemove.box)
            this.scene.remove(circleToRemove.torus)
            this.physics.remove(circleToRemove.physicObject)
            this.scene.remove(circleToRemove)
        }
    }

    teleport(sourcePortal, camera, body) {
        const halfTurn = new THREE.Quaternion();
        halfTurn.setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);
    
        const forwardDirection = new THREE.Vector3();
        camera.getWorldDirection(forwardDirection);
    
        const relativePos = sourcePortal.worldToLocal(camera.position.clone());
        relativePos.applyQuaternion(halfTurn);
        const newPos = sourcePortal.pair.localToWorld(relativePos)
        if (newPos.y < 9.98){
            newPos.y = 10
        }
        body.position.copy(newPos);
    
        const relativeRot = sourcePortal.quaternion.clone().invert().multiply(camera.quaternion);
        relativeRot.premultiply(halfTurn);
        camera.quaternion.copy(sourcePortal.pair.quaternion.clone().multiply(relativeRot));
    
        const newForwardDirection = new THREE.Vector3();
        camera.getWorldDirection(newForwardDirection);
        const targetRotation = new THREE.Quaternion().setFromRotationMatrix(
            new THREE.Matrix4().lookAt(
                new THREE.Vector3(0, 0, 0),
                newForwardDirection,
                new THREE.Vector3(0, 1, 0)
            )
        );
    
        camera.quaternion.copy(targetRotation);

        const inTransform = sourcePortal
        const outTransform = sourcePortal.pair

        if (body) {
            const worldVelocity = new CANNON.Vec3(body.velocity.x, body.velocity.y, body.velocity.z);
        
            const inTransformQuaternion = new CANNON.Quaternion(
                inTransform.quaternion.x,
                inTransform.quaternion.y,
                inTransform.quaternion.z,
                inTransform.quaternion.w
            );
        
            const outTransformQuaternion = new CANNON.Quaternion(
                outTransform.quaternion.x,
                outTransform.quaternion.y,
                outTransform.quaternion.z,
                outTransform.quaternion.w
            );
        
            const halfTurn = new CANNON.Quaternion();
            halfTurn.setFromAxisAngle(new CANNON.Vec3(0, 1, 0), Math.PI);
        
            const relativeVel = worldVelocity.clone();
            inTransformQuaternion.inverse().vmult(relativeVel, relativeVel);
        
            halfTurn.vmult(relativeVel, relativeVel);
        
            outTransformQuaternion.vmult(relativeVel, relativeVel);

            body.velocity.copy(relativeVel);
        }
    }    

    checkLeftPortalTeleport() {

        const portalForward = new THREE.Vector3()
        this.rightPortal[0].getWorldDirection(portalForward)

        const travelerPosition = this.game.camera.instance.position.clone()
        const portalPosition = this.rightPortal[0].position.clone()
        const portalToTraveler = travelerPosition.sub(portalPosition)

        const dotProduct = portalForward.dot(portalToTraveler)

        if (dotProduct < 0) {
            this.teleport(this.rightPortal[0],this.game.camera.instance,this.game.controls.cameraBody)
        }
    }

    checkRightPortalTeleport() {

        const portalForward = new THREE.Vector3()
        this.leftPortal[0].getWorldDirection(portalForward)

        const travelerPosition = this.game.camera.instance.position.clone()
        const portalPosition = this.leftPortal[0].position.clone()
        const portalToTraveler = travelerPosition.sub(portalPosition)

        const dotProduct = portalForward.dot(portalToTraveler)

        if (dotProduct < 0) {
            this.teleport(this.leftPortal[0],this.game.camera.instance,this.game.controls.cameraBody)
        }
    }


    resetPortalColors() {
        if (this.leftPortal.length > 0) {
            this.leftPortal[0].material.color = new THREE.Color(0xff9a00)
            this.leftPortal[0].material.needsUpdate = true
        }

        if (this.rightPortal.length > 0) {
            this.rightPortal[0].material.color = new THREE.Color(0x00a2ff)
            this.rightPortal[0].material.needsUpdate = true
        }
    }

    createCompanionCube() {
        const loader = new GLTFLoader();
        
        loader.load('/cube.glb', (gltf) => {
            this.companionCube = gltf.scene;
            this.companionCube.position.set(0, 2.5 * 2, -5);
            this.companionCube.scale.set(0.15, 0.15, 0.15);
            this.companionCube.castShadow = true;
            this.companionCube.receiveShadow = true;

            this.companionCube.traverse((child) => {
                if (child.isMesh && child.material) {
                    if (child.material.map) {
                        child.material.map.colorSpace = THREE.SRGBColorSpace;
                        child.material.metalness = .7
                    }
                    if (child.material.emissiveMap) {
                        child.material.emissiveMap.colorSpace = THREE.SRGBColorSpace;
                    }
                    child.material.needsUpdate = true;
                }
            });

            this.scene.add(this.companionCube);
    
            const cubeShape = new CANNON.Box(new CANNON.Vec3(2.5, 2.5, 2.5));
            this.companionCubeBody = new CANNON.Body({
                mass: 1,
                material: this.defaultMaterial,
                collisionFilterGroup: GROUP_CUBE,
                collisionFilterMask: GROUP_WALL | GROUP_PLAYER | GROUP_DEFAULT | GROUP_PORTAL,
            });
            this.companionCubeBody.addShape(cubeShape);
            this.companionCubeBody.position.copy(this.companionCube.position);
            this.companionCubeBody.linearDamping = 0.5;
            this.companionCubeBody.angularDamping = 0.5;
            this.companionCubeBody.class = 'companionCube';

            this.physics.addBody(this.companionCubeBody);

            this.companionCube.body = this.companionCubeBody;
        }, undefined, (error) => {
            console.error('An error occurred while loading the cube model:', error);
        });
    }

    getHoldPosition() {
        const camera = this.game.camera.instance;
        const cameraDirection = new THREE.Vector3();
        camera.getWorldDirection(cameraDirection);
        cameraDirection.normalize();

        return new THREE.Vector3().copy(camera.position)
            .add(cameraDirection.multiplyScalar(this.holdDistance));
    }
    
    onKeyDown(event) {
        if (event.key === 'e') {
            if (!this.isHoldingCube) {
                this.tryPickupCube();
            } else {
                this.dropCube();
            }
        }
    }

    tryPickupCube() {
        const playerPosition = this.game.camera.instance.position;
        const cubePosition = this.companionCube.position;
        const distance = playerPosition.distanceTo(cubePosition);

        if (distance <= this.pickupRange) {
            this.isHoldingCube = true;

            const holdPosition = this.getHoldPosition();
            this.holdBody = new CANNON.Body({
                mass: 0,
                type: CANNON.Body.KINEMATIC,
                collisionFilterGroup: 0,
                collisionFilterMask: 0
            });
            this.holdBody.position.copy(holdPosition);
            this.physics.addBody(this.holdBody);

            this.holdingConstraint = new CANNON.LockConstraint(this.companionCubeBody, this.holdBody);
            this.physics.addConstraint(this.holdingConstraint);

            this.companionCubeBody.collisionFilterMask &= ~GROUP_PLAYER;
            this.companionCubeBody.angularDamping = 1;
        }
    }

    dropCube() {
        this.isHoldingCube = false;

        if (this.holdingConstraint) {
            this.physics.removeConstraint(this.holdingConstraint);
            this.holdingConstraint = null;
        }

        if (this.holdBody) {
            this.physics.removeBody(this.holdBody);
            this.holdBody = null;
        }

        this.companionCubeBody.collisionFilterMask |= GROUP_PLAYER;
        this.companionCubeBody.angularDamping = 0.5;
    }

    checkCubePortalTeleport() {
        const cubePosition = new THREE.Vector3(
           this.companionCubeBody.position.x,
           this.companionCubeBody.position.y,
           this.companionCubeBody.position.z
        );
     
        const rightPortalPosition = this.rightPortal[0].position.clone();
        const rightPortalForward = new THREE.Vector3();
        this.rightPortal[0].getWorldDirection(rightPortalForward);
        const rightPortalToCube = cubePosition.clone().sub(rightPortalPosition);
        const rightDotProduct = rightPortalForward.dot(rightPortalToCube);
     
        if (rightDotProduct < 0) {
           this.teleport(this.rightPortal[0], this.companionCube, this.companionCubeBody);
        }
     
        const leftPortalPosition = this.leftPortal[0].position.clone();
        const leftPortalForward = new THREE.Vector3();
        this.leftPortal[0].getWorldDirection(leftPortalForward);
        const leftPortalToCube = cubePosition.clone().sub(leftPortalPosition);
        const leftDotProduct = leftPortalForward.dot(leftPortalToCube);
     
        if (leftDotProduct < 0) {
           this.teleport(this.leftPortal[0], this.companionCube, this.companionCubeBody);
        }
    }     
}