Source: components/VegetationBuilder.js

import { GLTFLoader } from '../../../lib/three/examples/jsm/loaders/GLTFLoader.js';
import { AnimationMixer, Object3D, LoopOnce } from 'three';

/**
 * Helper class to build trees.
 */
class VegetationBuilder {

    #mixer;
    #action;

    constructor() {
        this.bush = new Object3D();
        this.acaiPalm = new Object3D();
        this.datePalm = new Object3D();
        this.palm = new Object3D();
        this.seaPlant = new Object3D();
    }

    /**
     * @param {Number} x x coordinate of object
     * @param {Number} y y coordinate of object
     * @param {Number} z z coordinate of object
     * @param {Number} rotationY rotationY of object
     * @returns {THREE.Object3D} smol-tree
     */
    async loadTree(x, y, z, rotationY) {
        const loader = new GLTFLoader();
        const data = await loader.loadAsync('/assets/models/smol-tree.gltf');
        const model = data.scene;
        await this.addCastShadowRecursive(model);
        this.bush = model;
        this.bush.position.x = x;
        this.bush.position.y = y;
        this.bush.position.z = z;
        this.bush.rotation.y = rotationY;
        return this.bush;
    }

    async addCastShadowRecursive(model) {
        //Set castShadow to true for all children
        for(let i = 0; i < model.children.length; i++) {
            model.children[i].castShadow = true;
            model.children[i].receiveShadow = true;
            if(model.children[i].children.length > 0) {
                await this.addCastShadowRecursive(model.children[i]);
            }
        }
    }


    /**
     * @param {Number} x x coordinate of object
     * @param {Number} y y coordinate of object
     * @param {Number} z z coordinate of object
     * @param {Number} rotationY rotationY of object
     * @returns {THREE.Object3D} acaiPalm
     */
    async loadAcaiPalm(x, y, z, rotationY) {
        const loader = new GLTFLoader();
        const data = await loader.loadAsync('/assets/models/acai_palm/scene.gltf');
        const model = data.scene;
        await this.addCastShadowRecursive(model);
        model.scale.set(4, 4, 4);
        this.acaiPalm = model;
        this.acaiPalm.position.x = x;
        this.acaiPalm.position.y = y;
        this.acaiPalm.position.z = z;
        this.acaiPalm.rotation.y = rotationY;
        return this.acaiPalm;
    }

    /**
     * @param {Number} x x coordinate of object
     * @param {Number} y y coordinate of object
     * @param {Number} z z coordinate of object
     * @param {Number} rotationY rotationY of object
     * @returns {THREE.Object3D} datePalm
     */
    async loadDatePalm(x, y, z, rotationY) {
        const loader = new GLTFLoader();
        const data = await loader.loadAsync('/assets/models/date_palm/scene.gltf');
        const model = data.scene;
        await this.addCastShadowRecursive(model);
        model.scale.set(4, 4, 4);
        this.datePalm = model;
        this.datePalm.position.x = x;
        this.datePalm.position.y = y;
        this.datePalm.position.z = z;
        this.datePalm.rotation.y = rotationY;
        return this.datePalm;
    }

    /**
     * @param {Number} x x coordinate of object
     * @param {Number} y y coordinate of object
     * @param {Number} z z coordinate of object
     * @param {Number} rotationY rotationY of object
     * @returns {THREE.Object3D} PalmWithCoconut
     */
    async loadPalmWithCoconut(x, y, z, rotationY) {
        const loader = new GLTFLoader();
        const data = await loader.loadAsync('/assets/models/palm-tree.gltf');
        this.palm = this.setUpModel(data);
        this.palm.tick = (delta) => this.#mixer.update(delta);
        this.palm.startAnimation = () => this.startAnimation();
        this.palm.stopAnimation = () => this.#action.stop();
        this.palm.setInteractionText = this.setText;
        this.palm.position.x = x;
        this.palm.position.y = y;
        this.palm.position.z = z;
        this.palm.rotation.y = rotationY;
        return this.palm;
    }

    /**
     * @param {Number} x x coordinate of object
     * @param {Number} y y coordinate of object
     * @param {Number} z z coordinate of object
     * @param {Number} rotationY rotationY of object
     * @returns {THREE.Object3D} seaPlant
     */
    async loadSeaPlant(x, y, z, rotationY) {
        const loader = new GLTFLoader();
        const data = await loader.loadAsync('/assets/models/sea-plant.gltf');
        const model = data.scene;
        await this.addCastShadowRecursive(model);
        model.scale.set(2, 2, 2);
        this.seaPlant = model;
        this.seaPlant.position.x = x;
        this.seaPlant.position.y = y;
        this.seaPlant.position.z = z;
        this.seaPlant.rotation.y = rotationY;
        return this.seaPlant;
    }


    /**
     * Set the text of the interaction text
     */
    setText() {
        let div = document.getElementById("info");
        div.innerHTML = "E - Pflücken";
    }

    /**
     * Start the animation
     */
    async startAnimation() {
        this.#action.play();
    }

    /**
     * Set the Animations and Scale of the object 
     * @param {THREE.Object3D} data 
     * @returns THREE.Object3D
     */
    setUpModel(data) {
        const model = data.scene;
        this.addCastShadowRecursive(model);
        const clip = data.animations[0];
        this.#mixer = new AnimationMixer(model);
        this.#action = this.#mixer.clipAction(clip);
        this.#action.clampWhenFinished = true;
        this.#action.setLoop(LoopOnce);
        return model;
    }

}

export { VegetationBuilder };