Code viewer for World: Paint By Blocks v1 (Three.js)
//AB.socketStart();

// Load CSS
AB.loadCSS("/uploads/cadogav2/app-styles.css");

$.getScript("/uploads/cadogav2/three.r149.min.js", function(){
    console.log(this)
    startApp();
});

let rubikFont;

const jsonPath = "uploads/stewalsh/big-ben.json";

class UI {
    
    constructor(){
        this.selectedMaterial = null;
        this.isExporting = false;
        this.glbExporter = new GLTFExporter();
        this.objExporter = new OBJExporter();
        console.log('this.glbExporter')
        console.log(this.glbExporter)
    }
    
    init(){
        window.addEventListener("contextmenu", e => e.preventDefault());
        document.getElementById('ab-runheaderbox').remove()
        
        document.body.innerHTML += `<header class="menu-header">
            <nav class="menu-nav">
                <ul>
                    <li class="no-select">
                        <span>File</span>
                        <ul class="submenu">
                            <li>Load Model</li>
                            <li id="export_glb">Export Model as GLB</li>
                            <li id="export_obj">Export Model as OBJ</li>
                            <li>Save State</li>
                        </ul>
                    </li>
                    <li class="no-select"><span>Views</span></li>
                    <li class="no-select"><span>Help</span></li>
                </ul>

                <ul>
                    <i  id="undo_i_btn"
                        class="fa-solid fa-arrow-rotate-left disabled"
                        title="Undo"
                    ></i>
                    <i  id="redo_i_btn"
                        class="fa-solid fa-arrow-rotate-right disabled"
                        title="Redo"
                    ></i>
                </ul>

                <ul>
                    <i id="save_i_btn" class="fa-regular fa-floppy-disk" title="Save"></i>
                    <i class="fa-solid fa-download" title="Download Model"></i>
                </ul>

                <ul>
                    <i id="draw_i_btn" class="fa-solid fa-pen" title="Draw Block"></i>
                    <i id="paint_i_btn" class="fa-solid fa-brush" title="Paint Blocks"></i>
                    <i id="delete_i_btn" class="fa-solid fa-eraser" title="Delete Blocks"></i>
                </ul>

                <ul class="i-menu">
                    <i class="fa-solid fa-palette" title="Materials"></i>
                    <ul class="submenu">
                        <div class="grid-container">
                            <div class="item">
                                <img src="/uploads/cadogav2/mc-grass-side.png" />
                            </div>
                            <div class="item">
                                <img src="/uploads/cadogav2/mc-grass-side.png" />
                            </div>
                            <div class="item">
                                <img src="/uploads/cadogav2/mc-grass-side.png" />
                            </div>
                            <div class="item">
                                <img
                                    src="/uploads/cadogav2/mc-grass-side.png"
                                />
                            </div>
                            <div class="item">
                                <img src="/uploads/cadogav2/mc-grass-side.png" />
                            </div>
                            <div class="item">
                                <img src="/uploads/cadogav2/mc-grass-side.png" />
                            </div>
                        </div>
                    </ul>
                </ul>

                <ul>
                    <i id="camera_views_i_btn" class="fa-solid fa-camera" title="Camera Views"></i>
                    <i  id="camera_reset_btn"
                        class="fa-solid fa-house"
                        title="Center Camera to Origin"
                    ></i>
                </ul>
                <ul class="i-menu">
                    <i
                        class="fa-solid fa-circle-half-stroke"
                        title="Reference Opacity"
                    >
                    </i>
                    <ul class="submenu">
                        <input
                            type="range"
                            min="0"
                            max="1"
                            value="0.05"
                            step="0.05"
                            class="slider"
                            id="opacity-slider"
                        />
                    </ul>
                </ul>
                <ul>
                    <i class="fa-solid fa-computer-mouse"></i>
                </ul>
            </nav>
        </header>`;
        
        document.body.innerHTML += `<div id="load-display" class="dialog-box">
            <p>Loading Model...</p>
            <div id="progress-bar">
                <div id="progress"></div>
            </div>
            <div class="btn">Close</div>
        </div>`
        
        this.setupListeners();
    }
    
    setupListeners(){
        // home btn
        document.getElementById("camera_reset_btn").addEventListener('click',function(event){
            if (controls){
                controls.enableDamping = false;
                controls.reset();
                controls.enableDamping = true;
            }
        });
        
        // Export as OBJ
        document.getElementById("export_obj").addEventListener('click',(event) =>{
            console.log('this.glbExporter')
            console.log(this.glbExporter)
            if (blocksMap.size === 0) {
                console.log('There are no blocks to export');
                return;
            }
            
            
            // Check if export is already in progress
            if (this.isExporting) {
                console.log('Export is already in progress');
                return;
            }

            // Set the export flag
            this.isExporting = true;
            const group = new THREE.Group();
            group.add(...blocksMap.values());

            const result = this.objExporter.parse(group);

            const blob = new Blob([result], { type: 'text/plain' });
            saveAs(blob, 'objects.obj');
            this.isExporting = false;
            
        });
        
        // Export as GLB
        document.getElementById("export_glb").addEventListener('click',(event) =>{
            console.log('this.glbExporter')
            console.log(this.glbExporter)
            if (blocksMap.size === 0) {
                console.log('There are no blocks to export');
                return;
            }
            
            
            // Check if export is already in progress
            if (this.isExporting) {
                console.log('Export is already in progress');
                return;
            }

            // Set the export flag
            this.isExporting = true;
            const objectsToExport = [...blocksMap.values()]
            console.log('objectsToExport')
            console.log(objectsToExport)

            // Export the objects as GLB
            this.glbExporter.parse(objectsToExport, (result) => {
                // Save the GLB file
                console.log('parse')
                const blob = new Blob([result], { type: 'application/json' });
                console.log('saveAs')
                saveAs(blob, 'export.glb');

                // Reset the export flag
                this.isExporting = false;
            }, (error) => {
                console.error(error);
            });
            
        });
        
        document.getElementById("opacity-slider").addEventListener('input', function() {
            fs.refMaterial.opacity = parseFloat(this.value);
        });
        

        document.querySelectorAll(".item").forEach((item, index) => {
            item.addEventListener("click", () => {
                // unselect previously selected item
                if (this.selectedMaterial !== null) {
                    this.selectedMaterial.classList.remove("selected");
                }
                // select clicked item
                item.classList.add("selected");
                this.selectedMaterial = item;
                // log selected item's number
                console.log(`Selected item number: ${index + 1}`);
            });
        });
    }
}

class Utils {
    
    static updateMousePosition(mouse, event){
        mouse.set((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1);
    }
    
    static getMouseGridPosition(mouse, raycaster, floor){
        raycaster.setFromCamera(mouse, camera);
        let intersects = raycaster.intersectObjects([floor]);
        if (intersects.length > 0) {
            const point = intersects[0].point;
            const sqPosition = new THREE.Vector3(Math.floor(point.x), 0, Math.floor(point.z));
            
            return sqPosition;
        }
    }
    
    static getPreviewCubePosition(mouse, raycaster, floor){
        raycaster.setFromCamera(mouse, camera);
        let intersects = raycaster.intersectObjects([floor, ...blocksMap.values()]);
        
        if (intersects.length > 0) {
            let intersect = intersects[0];
            
            if (intersect.object.userData.type === 'block'){
                // Get face normal
                const norm = intersect.face.normal;
 
                const blockPosition = intersect.object.position;
                const rayPosition = new THREE.Vector3(Math.floor(blockPosition.x), Math.floor(blockPosition.y), Math.floor(blockPosition.z));

                return rayPosition.add(norm);
            } else if (intersect.object.userData.type === 'floor') {
                const point = intersect.point;
                const sqPosition = new THREE.Vector3(Math.floor(point.x), 0, Math.floor(point.z));
                
                return sqPosition;
            } else {
                return undefined;
            }
        }
    }
    
    static getMouseIntersect(mouse, raycaster, floor){
        raycaster.setFromCamera(mouse, camera);
        let intersects = raycaster.intersectObjects([floor,...blocksMap.values()]);
        if (intersects.length > 0) {
            return intersects[0];
        }
    }
    
    static fitCameraToModel(model, camera){
        const distance = 2;
        const box = new THREE.Box3().setFromObject(model);
        const size = box.getSize(new THREE.Vector3()).length();
        const center = box.getCenter(new THREE.Vector3());
        const direction = camera.position.clone().sub(center).normalize();
        camera.position.copy(direction.multiplyScalar(-distance * size).add(center));
        camera.lookAt(center);
    }
}

const Tools = {
    DRAW: "DRAW",
    PAINT: "PAINT",
    ERASE: "ERASE",
}

class Game {
    
    constructor(ui, textureManager){
        this.currentBlockMaterial = 1;
        this.currentTool = Tools.DRAW;
        this.mouse = new THREE.Vector2();
        this.raycaster = new THREE.Raycaster();
        this.previewBlock;
        this.floor;
        this.gridSize = 10;
        this.gridHelper;
        this.ui = ui;
        this.tm = textureManager;
    }
    
    init(){
        
        // Create grid
        this.gridHelper = new THREE.GridHelper(this.gridSize, this.gridSize, 0x7d8085, 0x4f5154);
        scene.add(this.gridHelper);
    
        this.createFloor();
        this.createPreviewCube();
        this.tm.init();
    }
    
    createFloor(){
        let geometry = new THREE.PlaneGeometry(this.gridSize, this.gridSize, this.gridSize, this.gridSize);
        let material = new THREE.ShadowMaterial();
        material.opacity = 0.2;
        this.floor = new THREE.Mesh(geometry, material);
        this.floor.rotation.x = -Math.PI / 2;
        this.floor.userData.type = 'floor';
        this.floor.receiveShadow = true;
        scene.add(this.floor);
    }
    
    createPreviewCube(){
        const geometry = new THREE.BoxGeometry(1,1,1);
        
        const material = new THREE.MeshPhysicalMaterial({
            color: 0x39e0fa, //0x40b8ff,
            emissive: 0x134359,//0x134359,
            roughness: 0.5,
            transmission: 0.75,
            thickness: 1,
            reflectivity: 0.5,
            specularIntensity: 0,
            transparent: true,
            
        });
        
        this.previewBlock = new THREE.Mesh(geometry, material);
        
        this.previewBlock.position.set(0.5, 0.5, 0.5);
        
        scene.add(this.previewBlock);
    }
    
    movePreviewBlock(){
        const sqPosition = Utils.getPreviewCubePosition(this.mouse, this.raycaster, this.floor);
    
        if (sqPosition !== undefined){
            this.previewBlock.visible = true;
            this.previewBlock.position.copy(sqPosition.clone().add(new THREE.Vector3(0.5, 0.5, 0.5)));
        } else {
            this.previewBlock.visible = false;
        }
    }
    
    addBlock(){
        const previewBlockPosition = this.previewBlock.position;
        const key = `${previewBlockPosition.x},${previewBlockPosition.y},${previewBlockPosition.z}`;
        const isBlockOverlapping = blocksMap.has(key);
        
        if (!isBlockOverlapping && this.previewBlock.visible === true){
            const geometry = new THREE.BoxGeometry(1, 1, 1);
            const material = new THREE.MeshStandardMaterial({ color: fs.colors[1] });
            //console.log(this.tm.materialList[0])
            const block = new THREE.Mesh(geometry, material);
            block.position.copy(this.previewBlock.position);
            block.userData.type = 'block';
            block.castShadow = true;
            block.receiveShadow = true;
            block.updateMatrixWorld(); // updates for next intersect
            scene.add(block);
            blocksMap.set(key, block);
            
            // Update preview cube position
            this.movePreviewBlock();

        } else {
            console.log('A cube already exists at this position or is out of range!')
        }
    }
    
    removeBlock(){
        if (blocksMap.size === 0) return;

        // Get intersected object
        const intersect = Utils.getMouseIntersect(this.mouse, this.raycaster, this.floor);
        
        if (intersect !== undefined) {
            if (intersect.object.userData.type === 'block'){
                const block = intersect.object;
                const key = `${block.position.x},${block.position.y},${block.position.z}`;
                scene.remove(block);
                blocksMap.delete(key);
            }
        }
    }
    
    updateGridHelper(newSize){
        scene.remove(this.gridHelper);
        this.gridSize = newSize;
        this.gridHelper = new THREE.GridHelper(this.gridSize, this.gridSize, 0x7d8085, 0x4f5154);
        scene.add(this.gridHelper);
        // Adjust the floor scale
        const scaleMultiplier = newSize / 10;
        this.floor.scale.set(scaleMultiplier, scaleMultiplier);
    }
}

class TextureManager {
    
    constructor(){
        this.loader = new THREE.TextureLoader();
        
        this.materialList = [];
    }
    
    init(){
        const sideGrassTexture = this.loader.load("/uploads/cadogav2/mc-grass-side.png");
        console.log('sideGrassTexture')
        console.log(sideGrassTexture)
        const grassMaterial = [
            new THREE.MeshStandardMaterial({map : sideGrassTexture, color: 0xffffff, flatShading: true, vertexColors: true,}),
            new THREE.MeshStandardMaterial({map : sideGrassTexture, color: 0xffffff, flatShading: true, vertexColors: true,}),
            new THREE.MeshStandardMaterial({map : this.loader.load("/uploads/cadogav2/mc-grass-top.png"), color: 0xffffff, flatShading: true, vertexColors: true,}),
            new THREE.MeshStandardMaterial({map : this.loader.load("/uploads/cadogav2/mc-grass-bottom.png"), color: 0xffffff, flatShading: true, vertexColors: true,}),
            new THREE.MeshStandardMaterial({map : sideGrassTexture, color: 0xffffff, flatShading: true, vertexColors: true,}),
            new THREE.MeshStandardMaterial({map : sideGrassTexture, color: 0xffffff, flatShading: true, vertexColors: true,})
        ];
        console.log('grassMaterial')
        console.log(grassMaterial)
        this.materialList.push(grassMaterial);
    }
}

class FileSystem {
    
    constructor(){
        this.fileLoader = new THREE.FileLoader();
        this.glbLoader = new GLTFLoader();
        this.xhr = new XMLHttpRequest();
        this.refMaterial = new THREE.MeshStandardMaterial({
            color: 0xffffff,
            opacity: 0.05,
            transparent: true,
            flatShading: true,
            vertexColors: true,
        });
        this.colors = {
          0: "#B8255F",
          1: "#FF9933",
          2: "#7ECC49",
          3: "#6ACCBC",
          4: "#4073FF",
          5: "#884DFF",
          6: "#EB96EB",
          7: "#FF8D85",
          8: "#FF5733",
          9: "#DAF7A6",
          10: "#FFEC59",
          11: "#C05780",
          12: "#6C88C4",
          13: "#74737A",
          14: "#CCCCFF",
          15: "#9FE2BF",
          16: "#8A3186",
          17: "#4057A7",
          18: "#042838",
          19: "#2DCED9",
          20: "#CBD6E2",
          21: "#4FB06D",
          22: "#BE398D",
          23: "#FF3B30",
          24: "#FFFFFF"
        };
        this.colorsLength = Object.keys(this.colors).length;
        this.importGLBModel = this.importGLBModel.bind(this);
    }
    
    importMineCraftJson(){
        let jsonData;
        this.fileLoader.load(jsonPath, (data) => {
            jsonData = JSON.parse(data);
            // Create blocks from json
            this.createAllBlocks(jsonData, 0.5);
        });
    }
    
    createBlockFromBlockData(blockData) {
        const from = blockData.from.map(x => Math.round(x));
        const to = blockData.to.map(x => Math.round(x));
        const width = to[0] - from[0];
        const height = to[1] - from[1];
        const depth = to[2] - from[2];
        const geometry = new THREE.BoxGeometry(width, height, depth);
        const colorIndex = blockData.color !== undefined? blockData.color % (this.colorsLength - 1) : Math.floor(Math.random() * (this.colorsLength - 1));
        const material = new THREE.MeshLambertMaterial({ color: this.colors[colorIndex] });
        const cube = new THREE.Mesh(geometry, material);
        // x, y, z
        cube.position.set(from[0] + width / 2, from[1] + height / 2, from[2] + depth / 2);
        return cube;
    }
    
    createAllBlocks(data, scaleFactor) {
        const group = new THREE.Group();
        
        data.elements.forEach(element => {
            const cube = this.createBlockFromBlockData(element);
            group.add(cube);
        });
        
        // Scale group
        group.scale.set(scaleFactor, scaleFactor, scaleFactor);
        
        // Transfer new scale directly to the children
        let position = new THREE.Vector3()
        let scale = new THREE.Vector3()
    
        group.children.forEach(child => {
            child.getWorldPosition(position)
            child.position.copy(position)
        
            child.getWorldScale(scale)
            child.scale.copy(scale);
        });
    
        // Add children to the scene
        scene.add(...group.children);
        
        // Clear the group
        group.children = [];
    }
    
    // IMPORT GLB
    importGLBModel(path, byteSize=0){
        $('#load-display').css('display', 'flex');
        
        // "uploads/stewalsh/mc-wh.glb"
        this.glbLoader.load("uploads/stewalsh/mc-sol.glb", (gltf) => {
                $('#load-display').css('display', 'none');
                // Get model
                const model = gltf.scene;
                this.centerToGrid(model)
        
        	    model.traverse((child) => {
                    if (child instanceof THREE.Mesh) {
                        // Use the child's own texture map for the material
                        if (child.material.map) {
                            this.refMaterial.map = child.material.map;
                            this.refMaterial.needsUpdate = true;
                            this.refMaterial.normalScale = child.material.normalScale;
                        }
                
                        // Set the material on the mesh
                        child.material = this.refMaterial;
                    }
                });
    
                console.log('model')
                console.log(model)
                Utils.fitCameraToModel(model, camera)
                
                const box = new THREE.BoxHelper( model, 0xffff00 );
                scene.add( box );
                // Add the model to the scene
                scene.add(model);
                console.log("game.floor")
                console.log(game.floor)
            },
    
            // Loading progress
            function (xhr) {
                let progress;
                
                if (byteSize === 0){
                    if (xhr.total > 0) {
                        progress = (xhr.loaded / xhr.total) * 100;
                    }
                } else {
                    progress = (xhr.loaded / byteSize) * 100;
                }
    
                console.log("progress", progress)
                $('#progress').width(progress + '%');
            },
    
            // Load failed
            function (error) {
                console.error("Error loading GLB file:", error);
            }
        );
    }
    
    centerToGrid(model) {
        // Get the bounding box of the model
        const box = new THREE.Box3().setFromObject(model);
        // Get the size of the bounding box
        const size = box.getSize(new THREE.Vector3());
        // Get center of the bounding box
        const center = box.getCenter(new THREE.Vector3());
        
        // Get the maximum dimension of the bounding box
        const maxDimension = Math.max(size.x, size.z);
        const gridSize = Math.ceil(maxDimension / 2) * 2;
        game.updateGridHelper(gridSize);
  
        // Center model
        model.position.x -= center.x;
        model.position.y -= box.min.y;
        model.position.z -= center.z;
        
        console.log("box",box)
        console.log("size",size)
        
        // Calculate the top-left corner of the rectangle
        const topLeftX = center.x - size.x / 2;
        const topLeftZ = center.z - size.z / 2;
        
        // Calculate the nearest rounded point and update the new rounded center coordinates
        const newCenterX = Math.round(topLeftX) + size.x / 2;
        const newCenterZ = Math.round(topLeftZ) + size.z / 2;
        
        const offsetX = gridSize % 2 === 0 && size.x % 2 === 0? 0 : 0.5;
        const offsetZ = gridSize % 2 === 0 && size.z % 2 === 0? 0 : 0.5;
        
        model.position.x += offsetX;
        model.position.z += offsetZ;
    }
}



function addEventListeners(){
    
    canvas.addEventListener('mousedown', onMouseDown);
    canvas.addEventListener('mousemove', onMouseMove);
    canvas.addEventListener('mouseup', onMouseUp);
    
    window.addEventListener("resize", () => {
        // Update sizes
        sizes.width = window.innerWidth;
        sizes.height = window.innerHeight;
    
        // Update camera
        camera.aspect = sizes.width / sizes.height;
        camera.updateProjectionMatrix();
    
        // Update renderer
        renderer.setSize(sizes.width, sizes.height);
        renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    });
    
}

function onMouseDown(event){
    console.log('click down', startDragging);
    
    mouseDownPos = {
        x: event.clientX,
        y: event.clientY
    };
    
    const intersect = Utils.getMouseIntersect(game.mouse, game.raycaster, game.floor);
    if (intersect === undefined){

        controls.enableRotate = true;
        startDragging = false;
        
        // force pointer event for orbit controls
        const pointerEvent = new PointerEvent('pointerdown', {
          bubbles: true,
          cancelable: true,
          pointerType: 'mouse',
          button: event.button,
          clientX: event.clientX,
          clientY: event.clientY,
        });
        controls.domElement.dispatchEvent(pointerEvent);
        return;
    }
    
    controls.enableRotate = false;
    
    cameraStartPosition.copy(camera.position);
    startDragging = true;
}

function onMouseMove(event){
    Utils.updateMousePosition(game.mouse, event);
    
    game.movePreviewBlock();
    
    if (startDragging){
        const distanceX = Math.abs(event.clientX - mouseDownPos.x);
        const distanceY = Math.abs(event.clientY - mouseDownPos.y);
        
        if (distanceX > mouseMoveThreshold || distanceY > mouseMoveThreshold) {
            mouseDownPos = {
                x: event.clientX,
                y: event.clientY
            };
            
            controls.enableRotate = true;
        }
    }
}

function onMouseUp(event){
    startDragging = false;
    
    console.log('click up============================================');
    // event.button, 0 = left, 1 = middle, 2 = right
    // console.log(event.button)

    Utils.updateMousePosition(game.mouse, event);
    
    // if camera position has been moved since mousedown, then exit function
    // to ignore add or remove block functions
    let startRounded = cameraStartPosition.clone().round();
    let cameraRounded = camera.position.clone().round();
    
    if (!startRounded.equals(cameraRounded)){
        return;
    }
    
    if (event.button === 0){
        game.addBlock();
    } else if (event.button === 2){
        game.removeBlock();
    }

}

const sizes = {
    width: window.innerWidth,
    height: window.innerHeight,
};

let canvas;
let camera;
let scene;
let renderer;
let controls;

let ui;
// TextureManager
let tm;
let game;
// FileSystem
let fs;
const blocksMap = new Map();
let glbLoader;


let mouseDownPos = null;
const mouseMoveThreshold = 10;
let cameraStartPosition = new THREE.Vector3();
let startDragging = false;



function startApp(){
    console.log('AB')
	console.log(AB)
	ui = new UI();
    ui.init();
    tm = new TextureManager();
    game = new Game(ui, tm);
    
    const container = document.getElementById("ab-runcanvas");

    // Create a canvas element
    canvas = document.createElement("canvas");
    container.appendChild(canvas);
    
    scene = new THREE.Scene();
	camera = new THREE.PerspectiveCamera(45, sizes.width / sizes.height, 0.1, 1000 );
	camera.position.x = -10;
    camera.position.y = 7.5;
    camera.position.z = 10;
	scene.add(camera);

    renderer = new THREE.WebGLRenderer({
        canvas: canvas,
        antialias: true,
        alpha: true,
        preserveDrawingBuffer : true 
    });
    renderer.setSize(sizes.width, sizes.height);
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    renderer.setClearColor(0xff0000, 0);
    //renderer.shadowMap.enabled = true;
    //renderer.sortObjects = false;
    //renderer.shadowMap.type = THREE.BasicShadowMap;
    
    // Lights
    let directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
    directionalLight.position.set(-5, 10, 10);
    directionalLight.castShadow = true;
    directionalLight.shadow.mapSize.width = 2048;
    directionalLight.shadow.mapSize.height = 2048;
    directionalLight.shadow.radius = 1;
    scene.add(directionalLight);
    
    let ambientLight = new THREE.AmbientLight(0xd3def5, 1)//(0x3258a8);
    scene.add(ambientLight);
    
    
    game.init();
    
    addEventListeners();
    
    fs = new FileSystem();

	
	controls = new OrbitControls(camera, canvas);
	controls.mouseButtons = {LEFT: 0, MIDDLE: 2};
	controls.dampingFactor = 0.15;
    controls.enableDamping = true;

    
    
    // AxesHelper
    const axesHelper = new THREE.AxesHelper();
    scene.add(axesHelper)
    
    // Import Model
    // fs.importMineCraftJson()
    fs.importGLBModel();
    

	
	const animate = () => {
        // const elapsedTime = clock.getElapsedTime();
    
        // Update controls
        controls.update();
    
        // Render
        renderer.render(scene, camera);
        //composer.render()
    
        // Call tick again on the next frame
        requestAnimationFrame(animate);
    };
    
    animate();
}