import { Vector3, TmpVectors, Matrix } from "../../Maths/math.vector.js";
import { Mesh } from "../mesh.js";
import { CreateRibbon, CreateRibbonVertexData } from "./ribbonBuilder.js";
import { Path3D } from "../../Maths/math.path.js";
/**
 * Creates a tube mesh.
 * The tube is a parametric shape. It has no predefined shape. Its final shape will depend on the input parameters
 * * The parameter `path` is a required array of successive Vector3. It is the curve used as the axis of the tube
 * * The parameter `radius` (positive float, default 1) sets the tube radius size
 * * The parameter `tessellation` (positive float, default 64) is the number of sides on the tubular surface
 * * The parameter `radiusFunction` (javascript function, default null) is a vanilla javascript function. If it is not null, it overrides the parameter `radius`
 * * This function is called on each point of the tube path and is passed the index `i` of the i-th point and the distance of this point from the first point of the path. It must return a radius value (positive float)
 * * The parameter `arc` (positive float, maximum 1, default 1) is the ratio to apply to the tube circumference : 2 x PI x arc
 * * The parameter `cap` sets the way the extruded shape is capped. Possible values : BABYLON.Mesh.NO_CAP (default), BABYLON.Mesh.CAP_START, BABYLON.Mesh.CAP_END, BABYLON.Mesh.CAP_ALL
 * * The optional parameter `instance` is an instance of an existing Tube object to be updated with the passed `pathArray` parameter. The `path`Array HAS to have the SAME number of points as the previous one: https://doc.babylonjs.com/features/featuresDeepDive/mesh/dynamicMeshMorph#tube
 * * You can also set the mesh side orientation with the values : BABYLON.Mesh.FRONTSIDE (default), BABYLON.Mesh.BACKSIDE or BABYLON.Mesh.DOUBLESIDE
 * * If you create a double-sided mesh, you can choose what parts of the texture image to crop and stick respectively on the front and the back sides with the parameters `frontUVs` and `backUVs` (Vector4). Detail here : https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/set#side-orientation
 * * The optional parameter `invertUV` (boolean, default false) swaps in the geometry the U and V coordinates to apply a texture
 * * The mesh can be set to updatable with the boolean parameter `updatable` (default false) if its internal geometry is supposed to change once created. The NUMBER of points CAN'T CHANGE, only their positions.
 * @param name defines the name of the mesh
 * @param options defines the options used to create the mesh
 * @param options.path
 * @param options.radius
 * @param options.tessellation
 * @param options.radiusFunction
 * @param options.cap
 * @param options.arc
 * @param options.updatable
 * @param options.sideOrientation
 * @param options.frontUVs
 * @param options.backUVs
 * @param options.instance
 * @param options.invertUV
 * @param options.updateVertexData
 * @param scene defines the hosting scene
 * @returns the tube mesh
 * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/param
 * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/set#tube
 */
export function CreateTube(name, options, scene = null) {
    const path = options.path;
    let instance = options.instance;
    let radius = 1.0;
    if (options.radius !== undefined) {
        radius = options.radius;
    }
    else if (instance) {
        radius = instance._creationDataStorage.radius;
    }
    const tessellation = options.tessellation || 64 | 0;
    const radiusFunction = options.radiusFunction || null;
    let cap = options.cap || Mesh.NO_CAP;
    const invertUV = options.invertUV || false;
    const updatable = options.updatable;
    const sideOrientation = Mesh._GetDefaultSideOrientation(options.sideOrientation);
    options.arc = options.arc && (options.arc <= 0.0 || options.arc > 1.0) ? 1.0 : options.arc || 1.0;
    // tube geometry
    const tubePathArray = (path, path3D, circlePaths, radius, tessellation, radiusFunction, cap, arc) => {
        const tangents = path3D.getTangents();
        const normals = path3D.getNormals();
        const distances = path3D.getDistances();
        const pi2 = Math.PI * 2;
        const step = (pi2 / tessellation) * arc;
        const returnRadius = () => radius;
        const radiusFunctionFinal = radiusFunction || returnRadius;
        let circlePath;
        let rad;
        let normal;
        let rotated;
        const rotationMatrix = TmpVectors.Matrix[0];
        let index = cap === Mesh.NO_CAP || cap === Mesh.CAP_END ? 0 : 2;
        for (let i = 0; i < path.length; i++) {
            rad = radiusFunctionFinal(i, distances[i]); // current radius
            circlePath = Array(); // current circle array
            normal = normals[i]; // current normal
            for (let t = 0; t < tessellation; t++) {
                Matrix.RotationAxisToRef(tangents[i], step * t, rotationMatrix);
                rotated = circlePath[t] ? circlePath[t] : Vector3.Zero();
                Vector3.TransformCoordinatesToRef(normal, rotationMatrix, rotated);
                rotated.scaleInPlace(rad).addInPlace(path[i]);
                circlePath[t] = rotated;
            }
            circlePaths[index] = circlePath;
            index++;
        }
        // cap
        const capPath = (nbPoints, pathIndex) => {
            const pointCap = Array();
            for (let i = 0; i < nbPoints; i++) {
                pointCap.push(path[pathIndex]);
            }
            return pointCap;
        };
        switch (cap) {
            case Mesh.NO_CAP:
                break;
            case Mesh.CAP_START:
                circlePaths[0] = capPath(tessellation, 0);
                circlePaths[1] = circlePaths[2].slice(0);
                break;
            case Mesh.CAP_END:
                circlePaths[index] = circlePaths[index - 1].slice(0);
                circlePaths[index + 1] = capPath(tessellation, path.length - 1);
                break;
            case Mesh.CAP_ALL:
                circlePaths[0] = capPath(tessellation, 0);
                circlePaths[1] = circlePaths[2].slice(0);
                circlePaths[index] = circlePaths[index - 1].slice(0);
                circlePaths[index + 1] = capPath(tessellation, path.length - 1);
                break;
            default:
                break;
        }
        return circlePaths;
    };
    let path3D;
    let pathArray;
    if (instance) {
        // tube update
        const storage = instance._creationDataStorage;
        const arc = options.arc || storage.arc;
        path3D = storage.path3D.update(path);
        pathArray = tubePathArray(path, path3D, storage.pathArray, radius, storage.tessellation, radiusFunction, storage.cap, arc);
        if (options.updateVertexData) {
            if (options.updatable) {
                const vertexData = CreateRibbonVertexData({
                    pathArray: pathArray,
                    sideOrientation: instance._originalBuilderSideOrientation,
                    closePath: true,
                    closeArray: false,
                    invertUV: invertUV,
                    frontUVs: options.frontUVs,
                    backUVs: options.backUVs,
                });
                vertexData.applyToMesh(instance, true);
            }
        }
        else {
            instance = CreateRibbon("", { pathArray: pathArray, instance: instance });
        }
        // Update mode, no need to recreate the storage.
        storage.path3D = path3D;
        storage.pathArray = pathArray;
        storage.arc = arc;
        storage.radius = radius;
        return instance;
    }
    // tube creation
    path3D = new Path3D(path);
    const newPathArray = new Array();
    cap = cap < 0 || cap > 3 ? 0 : cap;
    pathArray = tubePathArray(path, path3D, newPathArray, radius, tessellation, radiusFunction, cap, options.arc);
    const tube = CreateRibbon(name, {
        pathArray: pathArray,
        closePath: true,
        closeArray: false,
        updatable: updatable,
        sideOrientation: sideOrientation,
        invertUV: invertUV,
        frontUVs: options.frontUVs,
        backUVs: options.backUVs,
    }, scene);
    tube._creationDataStorage.pathArray = pathArray;
    tube._creationDataStorage.path3D = path3D;
    tube._creationDataStorage.tessellation = tessellation;
    tube._creationDataStorage.cap = cap;
    tube._creationDataStorage.arc = options.arc;
    tube._creationDataStorage.radius = radius;
    return tube;
}
/**
 * Class containing static functions to help procedurally build meshes
 * @deprecated use CreateTube directly
 */
export const TubeBuilder = {
    // eslint-disable-next-line @typescript-eslint/naming-convention
    CreateTube,
};
Mesh.CreateTube = (name, path, radius, tessellation, radiusFunction, cap, scene, updatable, sideOrientation, instance) => {
    const options = {
        path: path,
        radius: radius,
        tessellation: tessellation,
        radiusFunction: radiusFunction,
        arc: 1,
        cap: cap,
        updatable: updatable,
        sideOrientation: sideOrientation,
        instance: instance,
    };
    return CreateTube(name, options, scene);
};
//# sourceMappingURL=tubeBuilder.js.map