Source: Mesh3D.js

import { Color, Mesh } from "three";
import { LoopSubdivision } from "three-subdivide";
/**
 * 3D mesh with specific geometry and material
 * @hideconstructor
 */
export class Mesh3D extends Mesh {

    constructor(geometry, material) {
        super(geometry, material);
    }

    /**
     * Set the rotation along X axis
     * 
     * @param {number} angle - rotation along X in degrees or radians
     * @returns {Mesh3D} modified object
     */
    setRotationX(angle) {
        if (p5.instance._angleMode === p5.prototype.DEGREES) {
            angle = p5.prototype.radians(angle);
        }
        this.rotation.x = angle;
        return this;
    }
    /**
     * Set the rotation along Y axis
     * 
     * @param {number} angle - rotation along Y in degrees or radians
     * @returns {Mesh3D} modified object
     */
    setRotationY(angle) {
        if (p5.instance._angleMode === p5.prototype.DEGREES) {
            angle = p5.prototype.radians(angle);
        }
        this.rotation.y = angle;
        return this;
    }
    /**
     * Set the rotation along Z axis
     * 
     * @param {number} angle - rotation along Z in degrees or radians
     * @returns {Mesh3D} modified object
     */
    setRotationZ(angle) {
        if (p5.instance._angleMode === p5.prototype.DEGREES) {
            angle = p5.prototype.radians(angle);
        }
        this.rotation.z = angle;
        return this;
    }

    /**
     * 
     * Set rotation of a Mesh3D.
     * 
     * @param {Number|Vector} x rotation along X in degrees or radians or a p5.Vector object (in this case ony one parameter is needed)
     * @param {Number} y rotation along Y in degrees or radians
     * @param {Number} z rotation along Z in degrees or radians
     * @returns {Mesh3D} modified object
     */

    setRotation(x = 0, y = 0, z = 0) {
        //console.log(p5.instance._angleMode)
        if (x.isVector3 || x.angleBetween) {
            const tempVector = {
                x: x.x,
                y: x.y,
                z: x.z
            }

            x = tempVector.x;
            y = tempVector.y;
            z = tempVector.z;
        }

        if (p5.instance._angleMode === p5.prototype.DEGREES) {
            x = p5.prototype.radians(x);
            y = p5.prototype.radians(y);
            z = p5.prototype.radians(z);
        }

        this.rotation.set(x, y, z);
        return this;
    }
    /**
     * 
     * Set X position
     * 
     * @param {Number} X position    
     * @returns {Mesh3D} modified object
     */

    setPositionX(value) {
        this.position.x = value;
        return this;
    }

    /**
     * 
     * Set Y position
     * 
     * @param {Number} Y position    
     * @returns {Mesh3D} modified object
     */

    setPositionY(value) {
        this.position.y = value;
        return this;
    }

    /**
     * 
     * Set Z position
     * 
     * @param {Number} Z position    
     * @returns {Mesh3D} modified object
     */

    setPositionZ(value) {
        this.position.z = value;
        return this;
    }

    /**
     * 
     * Set position of a Mesh3D.
     * 
     * @param {Number|Vector} x X coordinate or a p5.Vector with x,y,z (in this case ony one parameter is needed)
     * @param {Number} y Y coordinate
     * @param {Number} z Z coordinate
     * @returns {Mesh3D} modified object
     */

    setPosition(x = 0, y = 0, z = 0) {
        if (x.isVector3 || x.angleBetween) {
            const tempVector = {
                x: x.x,
                y: x.y,
                z: x.z
            }

            x = tempVector.x;
            y = tempVector.y;
            z = tempVector.z;
        }

        this.position.set(x, y, z);
        return this;
    }

    /**
     * Set X scale.
     * 
     * @param {Number} x X coordinate
     * @returns {Mesh3D} modified object
     */

    setScaleX(value) {
        this.scale.x = value;
        return this;
    }

    /**
     * Set Y scale.
     * 
     * @param {Number} y Y coordinate
     * @returns {Mesh3D} modified object
     */

    setScaleY(value) {
        this.scale.y = value;
        return this;
    }

    /**
     * Set Z scale.
     * 
     * @param {Number} z Z coordinate
     * @returns {Mesh3D} modified object
     */

    setScaleZ(value) {
        this.scale.z = value;
        return this;
    }
    /**
     * If 1 parameter is used it can be a Number (in this case x,y,z are equals) or a Vector.
     * If 3 parameters are used they will be X,Y and Z scale.
     * 
     * @param  {...any} values 1 or 3 Numbers or 1 Vector to define new scale.
     * @returns modified object
     */

    setScale(...values) {
        if (values.length != 1 && values.length != 3) {
            console.error("Use setScale with 1 or 3 parameters setScale(vector|x,y,z)")
            return;
        }

        var x = 0.1;
        var y = 0.1;
        var z = 0.1;

        //console.log(p5.instance._angleMode)
        if (values[0].isVector3 || values[0].angleBetween) {
            const tempVector = {
                x: values[0].x,
                y: values[0].y,
                z: values[0].z
            }

            x = tempVector.x;
            y = tempVector.y;
            z = tempVector.z;
        } else if (values.length == 1) {
            x = values[0];
            y = values[0];
            z = values[0];
        } else {
            x = values[0];
            y = values[1];
            z = values[2];
        }
        this.scale.set(x, y, z);
        return this;
    }
    /**
     * 
     * Get current rotation
     * 
     * @returns {Vector} rotation vector
     */

    getRotation() {
        const angleMode = p5.instance._angleMode;
        const rotX = angleMode == p5.prototype.RADIANS ? this.rotation.x : p5.prototype.degrees(this.rotation.x);
        const rotY = angleMode == p5.prototype.RADIANS ? this.rotation.y : p5.prototype.degrees(this.rotation.y);
        const rotZ = angleMode == p5.prototype.RADIANS ? this.rotation.z : p5.prototype.degrees(this.rotation.z);

        return new p5.Vector(rotX, rotY, rotZ);
    }

    getRotationX() {
        const angleMode = p5.instance._angleMode;
        return angleMode == p5.prototype.RADIANS ? this.rotation.x : p5.prototype.degrees(this.rotation.x);
    }

    getRotationY() {
        const angleMode = p5.instance._angleMode;
        return angleMode == p5.prototype.RADIANS ? this.rotation.y : p5.prototype.degrees(this.rotation.y);
    }

    getRotationZ() {
        const angleMode = p5.instance._angleMode;
        return angleMode == p5.prototype.RADIANS ? this.rotation.z : p5.prototype.degrees(this.rotation.z);
    }

    /**
     * 
     * Get current position
     * 
     * @returns {Vector} position vector
     */

    getPosition() {
        return new p5.Vector(this.position.x, this.position.y, this.position.z);
    }

    /**
     * 
     * Get this object X position
     * 
     * @returns {Number} position X coordinate
     */

    getPositionX() {
        return this.position.x;
    }

    /**
     * 
     * Get this object Y position
     * 
     * @returns {Number} position Y coordinate
     */

    getPositionY() {
        return this.position.y;
    }

    /**
     * 
     * Get this object Z position
     * 
     * @returns {Number} position Z coordinate
     */

    getPositionZ() {
        return this.position.z;
    }

    /**
     * 
     * Get current scale
     * 
     * @returns {Vector} scale vector
     */

    getScale() {
        return new p5.Vector(this.scale.x, this.scale.y, this.scale.z);
    }

    /**
     * 
     * Get this object X scale
     * 
     * @returns {Number} scale X coordinate
     */

    getScaleX() {
        return this.scale.x;
    }

    /**
     * 
     * Get this object Y scale
     * 
     * @returns {Number} scale Y coordinate
     */

    getScaleY() {
        return this.scale.y;
    }

    /**
     * 
     * Get this object Z scale
     * 
     * @returns {Number} scale Z coordinate
     */

    getScaleZ() {
        return this.scale.z;
    }

    /**
     * Get this object roughness.
     * 
     * @returns {Number} current roughness
     */

    getRoughness() {
        return this.material.roughness;
    }

    /**
     * Get this object metalness.
     * 
     * @returns {Number} current metalness
     */

    getMetalness() {
        return this.material.metalness;
    }

    /**
     * Get this object diffuse color.
     * 
     * @returns {Number} current diffuse color
     */

    getColor() {
        return this.material.color.toString();
    }


    /**
     * Get this object opacity.
     * 
     * @returns {Number} current opacity
     */

    getOpacity() {
        return this.material.opacity;
    }

    /**
     * Get this object material.
     * 
     * @returns {Material} a copy of this object material
     */

    getMaterial = () => {
        return this.material.clone();
    }

    /**
     * Set roughness for this object. Default is 1
     * 
     * @param {Number} value new roughness
     * @returns {Mesh3D} modified object
     */

    setRoughness(value) {
        this.roughness = value;
        this.traverse((o) => {
            if (o.isMesh && o.material) {
                o.material.roughness = value;
            }
        })
        return this;
    }

    /**
    * Set metalness for this object. Default is 0
    * 
    * @param {Number} value new metalness
    * @returns {Mesh3D} modified object
    */

    setMetalness(value) {
        this.material.metalness = value;
        this.traverse((o) => {
            if (o.isMesh && o.material) {
                o.material.metalness = value;
            }
        })
        return this;
    }

    /**
     * 
     * Change object color. You can use string, another Color or r,g,b parameters.
     * 
     * <pre>
     * mesh.setColor("red");
     * mesh.setColor("#ff0000");
     * mesh.setColor(255, 0, 1);
     * </pre>
     * 
     * @param  {Color} color new color. 
     * @returns {Mesh3D} modified object
     */

    setColor(...args) {
        var cTemp = p5.instance.color(...args);
        var color = new Color(cTemp.toString('rgb'));
        var newMaterial = this.material.clone();
        this.material = newMaterial;
        this.material.color = color;
        this.traverse((o) => {
            if (o.isMesh && o.material) {
                o.material.color = color;
            }
        })
        return this;
    }


    /**
    * Set opacity for this object. Default is 1.
    * 
    * @param {Number} value new opacity: 0 is completely transparent, 1 is opaque
    * @returns {Mesh3D} modified object
    */

    setOpacity = (opacity = 1.0) => {
        if (this.material) {
            this.material.opacity = opacity;
            if (this.material.opacity < 1)
                this.material.transparent = true;
            else
                this.material.transparent = false;


            this.traverse((o) => {
                if (o.isMesh && o.material) {
                    o.material.opacity = opacity;
                    if (o.material.opacity < 1)
                        o.material.transparent = true;
                    else
                        o.material.transparent = false;
                }
            })
        }
        return this;
    }

    /**
     * Change the material of this object to a copy of the provided one.
     * 
     * @param {Material} material a material to be copied
     * @returns {Mesh3D} modified object
     */

    setMaterial = (material) => {
        this.material = material.clone();
        return this;
    }

    /**
     * Subdivides the geometry of the Mesh3D object using Loop subdivision.
     * 🧪 This is an experimental feature: ⚠️ use with caution.
     * 
     * @param {number} subdivisions - The number of subdivisions to perform.
     * @param {boolean} [split=true] - Whether or not to split the vertices at boundaries.
     * @param {boolean} [uvSmooth=false] - Whether or not to smooth UVs.
     * @param {boolean} [preserveEdges=false] - Whether or not to preserve edges.
     * @param {boolean} [flatOnly=false] - Whether or not to only apply the algorithm to flat faces.
     * @param {number} [maxTriangles=3000] - The maximum number of triangles to generate.
     * @returns {Mesh3D} The Mesh3D object with the updated geometry.
     */
    subdivide = (subdivisions = 1, split = true, uvSmooth = false, preserveEdges = false, flatOnly = false, maxTriangles = 3000) => {
        subdivisions =  min(5, max(0, floor(subdivisions)));
        const params = {
            split: split,
            uvSmooth: uvSmooth,
            preserveEdges: preserveEdges,
            flatOnly: flatOnly,
            maxTriangles: Math.floor(maxTriangles)
        };
        this.geometry = LoopSubdivision.modify(this.geometry, subdivisions, params);
        return this;
    }
}