Source: sol_scene.js

import { OrbitControl } from "./OrbitControl.js";
import {
    Scene, Vector4, MeshBasicMaterial, ShapeGeometry, ArrayCamera, MeshLambertMaterial, DirectionalLight, PerspectiveCamera, AmbientLight, PointLightHelper, WebGLRenderer, PointLight, BoxGeometry, DodecahedronGeometry, CylinderGeometry,
    SphereGeometry, MeshPhongMaterial, Mesh, PlaneGeometry, Color, PCFSoftShadowMap, Raycaster, Vector2, Vector3, RectAreaLight, AxesHelper
} from "./three.js";

/**
 * Represents the sol_scene object.
 * @type {Scene}
 */
const sol_scene = new Scene();
/**
 * Represents a camera in the 3D scene.
 * @type {PerspectiveCamera}
 */
const camera = new PerspectiveCamera();
sol_scene.background = new Color("rgb(188,244,250)");
const globalLight = new AmbientLight(0xeeeeee);
sol_scene.add(globalLight);

const light = new PointLight(0xBCF4FA, 15, 0);
light.castShadow = true;
const helper = new PointLightHelper(light, 2);
sol_scene.add(light);
sol_scene.add(helper);
light.intensity = 0.5;
light.position.set(0, 0, 1).normalize();

const renderer = new WebGLRenderer({ antialias: true });

renderer.shadowMap.enabled = true;
renderer.shadowMap.type = PCFSoftShadowMap;
renderer.setClearColor(0x999999);

let resizeObeserver;
let firstPlacementCoord = null;
let currentShapePlacements = [];

/**
 * Represents the input shapes for the sol scene.
 * @typedef {Object} sol_inputShapes
 * @property {Function} get - Retrieves the input shapes.
 * @property {Function} add - Adds a shape to the input shapes.
 * @property {Function} clear - Clears the input shapes.
 * @property {Array} store - The array that stores the input shapes.
 */
export let sol_inputShapes = {
    get() {
        return this.store;
    },
    add(shape_name) {
        this.store.push(shape_name);
    },
    clear() {
        this.store = [];
    },
    store: []
};

export let sol_inputCoords = {
    get() {
        return this.store;
    },
    add(coord) {
        this.store.push(coord);
    },
    clear() {
        this.store = [];
    },
    store: []
};

/**
 * Object representing the colours used in the sol scene.
 * @typedef {Object} sol_Colours
 * @property {number} A - Colour A represented as hexadecimal value.
 * @property {number} B - Colour B represented as hexadecimal value.
 * @property {number} C - Colour C represented as hexadecimal value.
 * @property {number} D - Colour D represented as hexadecimal value.
 * @property {number} E - Colour E represented as hexadecimal value.
 * @property {number} F - Colour F represented as hexadecimal value.
 * @property {number} G - Colour G represented as hexadecimal value.
 * @property {number} H - Colour H represented as hexadecimal value.
 * @property {number} I - Colour I represented as hexadecimal value.
 * @property {number} J - Colour J represented as hexadecimal value.
 * @property {number} K - Colour K represented as hexadecimal value.
 * @property {number} L - Colour L represented as hexadecimal value.
 */
const sol_Colours = {
    A: 0x228B1E,
    B: 0x6D359A,
    C: 0x1E9195,
    D: 0x931515,
    E: 0xA2A42C,
    F: 0x9F1B92,
    G: 0x904512,
    H: 0x0E2B0C,
    I: 0x272899,
    J: 0x966E9A,
    K: 0x205F90,
    L: 0x9DA15E,
};

/**
 * Initializes the scene with the given canvas element.
 * 
 * @param {HTMLCanvasElement} sol_canvas - The canvas element to render the scene on.
 */
export function initScene(sol_canvas) {
    camera.fov = 75;
    camera.near = 0.2;
    camera.far = 300;
    camera.position.z = 18;
    camera.position.x = -0;
    camera.position.y = 0;

    renderer.setSize(sol_canvas.clientWidth, sol_canvas.clientWidth);
    resizeObeserver = new ResizeObserver(entries => {
        entries.forEach(entry => {
            camera.aspect = sol_canvas.clientWidth / sol_canvas.clientHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(sol_canvas.clientWidth, sol_canvas.clientWidth);
        })
    });
    resizeObeserver.observe(sol_canvas);

    const controls = new OrbitControl(camera, renderer.domElement);
    controls.enablePan = false;
    controls.enableDamping = true;
    controls.dampingFactor = 0.05;
    controls.screenSpacePanning = false;
    controls.maxDistance = 300;

    controls.target = new Vector3(5, 3.8, 5);
    controls.maxPolarAngle = Math.PI / 2;

    function arrayCoordsFromWorldCoords(x, y, height) {
        let layer = Math.round((height - 1) / Math.sqrt(2));
        let x_index;
        let y_index;
        if (layer % 2 === 1) {
            x_index = (x - 1 - 1 * layer) / 2;
            y_index = (y - 1 - 1 * layer) / 2;
        } else {
            x_index = (x - 1 - 1 * layer) / 2;
            y_index = (y - 1 - 1 * layer) / 2;
        }
        return [x_index, y_index, layer];
    }

    function setInput(shape, coord) {
        if (!(sol_inputShapes.get().includes(shape))) {
            sol_inputShapes.add(shape);
            sol_inputCoords.add(new Array(coord));
        } else {
            sol_inputCoords.get()[sol_inputShapes.get().indexOf(shape)].push(coord);
        }
    }

    const raycaster = new Raycaster();
    const pointer = new Vector2();
    function animate() {
        renderer.render(sol_scene, camera);
        controls.update();
        requestAnimationFrame(animate);
    }

    sol_canvas.appendChild(renderer.domElement);

    const meshfloor = new Mesh(
        new PlaneGeometry(130, 130, 10, 10),
        new MeshPhongMaterial({
            color: 0xBCF4FA,
            wireframe: false
        })
    )
    meshfloor.rotation.x -= Math.PI / 2;
    meshfloor.receiveShadow = true;

    light.position.set(4, 20, 4);

    animate();
}
/**
 * Creates a sphere with the specified position, color, radius, and number of segments.
 * @param {number} x - The x-coordinate of the sphere's position.
 * @param {number} y - The y-coordinate of the sphere's position.
 * @param {number} z - The z-coordinate of the sphere's position.
 * @param {string} color - The color of the sphere.
 * @param {number} radius - The radius of the sphere.
 * @param {number} segs - The number of segments used to create the sphere.
 * @returns {Mesh} The created sphere mesh.
 */
function createSphere(x, y, z, color, radius, segs) {
    let mat = new MeshPhongMaterial({
        color: color,
        specular: color,
        shininess: 30
    });
    mat.castShadow = true;
    mat.receiveShadow = true;
    let sphere = new Mesh(new SphereGeometry(radius, segs, segs), mat);
    sphere.position.set(x, z, y);
    sphere.castShadow = true;
    sphere.receiveShadow = true;
    sphere.name = ["s", x, y, z].join(",");
    return sphere;
}

function disposeSphere(instance) {
    sol_scene.remove(instance);
    instance.material.dispose();
    instance.dispose();
}

/**
 * Represents a SolScene object.
 * @class
 */
export default class {
    /**
     * Creates a sphere object.
     * @param {number} x - The x-coordinate of the sphere.
     * @param {number} y - The y-coordinate of the sphere.
     * @param {number} z - The z-coordinate of the sphere.
     * @param {string} color - The color of the sphere.
     * @param {number} [radius=1] - The radius of the sphere.
     * @param {number} [segs=15] - The number of segments of the sphere.
     * @returns {object} The created sphere object.
     */
    createSphere(x, y, z, color, radius = 1, segs = 15) {
        return createSphere(x, y, z, color, radius, segs);
    }

    /**
     * Disposes a sphere object.
     * @param {object} sphere - The sphere object to dispose.
     */
    disposeSphere(sphere) {
        disposeSphere(sphere);
    }

    /**
     * Adds an object to the SolScene.
     * @param {object} obj - The object to add.
     */
    add(obj) {
        sol_scene.add(obj);
    }

    /**
     * Initializes the SolScene.
     * @param {object} dom - The DOM element to attach the scene to.
     */
    sol_init(dom) {
        initScene(dom);
    }

    /**
     * Disposes the SolScene.
     */
    dispose() {
        resizeObeserver.disconnect();
        cancelAnimationFrame();
    }
};

function resetFirstPlacementCoord() {
    firstPlacementCoord = null;
}

export { sol_Colours };