Source: components/BallBuilder.js

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

import { GRAVITY } from "../World.js";

/**
 * Helper class to build a ball.
 */
class BallBuilder {
    constructor() {
        this.soccerBall = new Object3D();
    }

    /**
     * Creates a ball.
     * @param {Number} x Position in x axis
     * @param {Number} y Position in y axis
     * @param {Number} z Position in z axis
     * @param {Number} rotationY Rotation in y axis
     * @returns THREE.Object3D
     */
    async loadSoccerBall(x, y, z, rotationY) {
        const loader = new GLTFLoader();
        const data = await loader.loadAsync('/assets/models/soccer-ball.gltf');

        this.soccerBall = this.setUpModel(data);

        this.soccerBall.position.x = x;
        this.soccerBall.position.y = y;
        this.soccerBall.position.z = z;
        this.soccerBall.rotation.y = rotationY;
        this.yRaycaster = new Raycaster(this.soccerBall.position.clone(), new Vector3(0, -1, 0), 0, 30);

        this.soccerBall.weight = 10;
        this.soccerBall.GRAVITY = GRAVITY.clone();
        this.soccerBall.acceleration = new Vector3();
        this.soccerBall.velocity = new Vector3();
        this.soccerBall.kicked = false;
        this.soccerBall.kick = this.kick;
        this.soccerBall.applyForce = this.applyForce;
        this.soccerBall.update = this.update;
        this.soccerBall.setInteractionText = this.setText;

        this.soccerBall.tick = (delta) => this.soccerBall.update();

        return this.soccerBall;
    }

    /**
     * Kick the ball in that direction
     * @param {THREE.Vector3} direction
     */
    kick(direction) {
        if (!this.kicked) {
            direction.normalize();
            this.applyForce(direction.multiplyScalar(10));
            this.kicked = true;
        }
    }

    /**
     * Apply a force to the ball
     * @param {THREE.Vector3} force 
     */
    applyForce(force) {
        const vector = force.clone();
        vector.divideScalar(this.weight);
        this.acceleration.add(vector);
    }

    /**
     * Update the position of the ball
     */
    update() {
        this.applyForce(this.GRAVITY);

        this.velocity.add(this.acceleration);
        this.position.add(this.velocity);

        this.acceleration.multiplyScalar(0);
    }

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

    /**
     * Set the Animations and Scale of the Ball
     * @param {THREE.Object3D} data 
     * @returns THREE.Object3D
     */
    setUpModel(data) {
        const model = data.scene;
        //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) {
                for(let j = 0; j < model.children[i].children.length; j++) {
                    model.children[i].children[j].castShadow = true;
                    model.children[i].children[j].receiveShadow = true;
                }
            }
        }
        model.scale.set(15, 15, 15);
        return model;
    }

}

export { BallBuilder };