(function (global, factory) {
  if (typeof define === "function" && define.amd) {
    define(["exports", "three"], factory);
  } else if (typeof exports !== "undefined") {
    factory(exports, require("three"));
  } else {
    var mod = {
      exports: {}
    };
    factory(mod.exports, global.three);
    global.CurveModifier = mod.exports;
  }
})(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : this, function (_exports, _three) {
  "use strict";

  Object.defineProperty(_exports, "__esModule", {
    value: true
  });
  _exports.InstancedFlow = _exports.Flow = void 0;
  _exports.getUniforms = getUniforms;
  _exports.initSplineTexture = initSplineTexture;
  _exports.modifyShader = modifyShader;
  _exports.updateSplineTexture = updateSplineTexture;

  function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } Object.defineProperty(subClass, "prototype", { value: Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }), writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); }

  function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }

  function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }

  function _possibleConstructorReturn(self, call) { if (call && (typeof call === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); }

  function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }

  function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }

  function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }

  function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

  function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

  function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }

  // Original src: https://github.com/zz85/threejs-path-flow
  var BITS = 3;
  var TEXTURE_WIDTH = 1024;
  var TEXTURE_HEIGHT = 4;

  /**
   * Make a new DataTexture to store the descriptions of the curves.
   *
   * @param { number } numberOfCurves the number of curves needed to be described by this texture.
   */
  function initSplineTexture() {
    var numberOfCurves = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
    var dataArray = new Float32Array(TEXTURE_WIDTH * TEXTURE_HEIGHT * numberOfCurves * BITS);
    var dataTexture = new _three.DataTexture(dataArray, TEXTURE_WIDTH, TEXTURE_HEIGHT * numberOfCurves, _three.RGBFormat, _three.FloatType);
    dataTexture.wrapS = _three.RepeatWrapping;
    dataTexture.wrapY = _three.RepeatWrapping;
    dataTexture.magFilter = _three.NearestFilter;
    dataTexture.needsUpdate = true;
    return dataTexture;
  }
  /**
   * Write the curve description to the data texture
   *
   * @param { DataTexture } texture The DataTexture to write to
   * @param { Curve } splineCurve The curve to describe
   * @param { number } offset Which curve slot to write to
   */


  function updateSplineTexture(texture, splineCurve) {
    var offset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
    var numberOfPoints = Math.floor(TEXTURE_WIDTH * (TEXTURE_HEIGHT / 4));
    splineCurve.arcLengthDivisions = numberOfPoints / 2;
    splineCurve.updateArcLengths();
    var points = splineCurve.getSpacedPoints(numberOfPoints);
    var frenetFrames = splineCurve.computeFrenetFrames(numberOfPoints, true);

    for (var i = 0; i < numberOfPoints; i++) {
      var rowOffset = Math.floor(i / TEXTURE_WIDTH);
      var rowIndex = i % TEXTURE_WIDTH;
      var pt = points[i];
      setTextureValue(texture, rowIndex, pt.x, pt.y, pt.z, 0 + rowOffset + TEXTURE_HEIGHT * offset);
      pt = frenetFrames.tangents[i];
      setTextureValue(texture, rowIndex, pt.x, pt.y, pt.z, 1 + rowOffset + TEXTURE_HEIGHT * offset);
      pt = frenetFrames.normals[i];
      setTextureValue(texture, rowIndex, pt.x, pt.y, pt.z, 2 + rowOffset + TEXTURE_HEIGHT * offset);
      pt = frenetFrames.binormals[i];
      setTextureValue(texture, rowIndex, pt.x, pt.y, pt.z, 3 + rowOffset + TEXTURE_HEIGHT * offset);
    }

    texture.needsUpdate = true;
  }

  function setTextureValue(texture, index, x, y, z, o) {
    var image = texture.image;
    var data = image.data;
    var i = BITS * TEXTURE_WIDTH * o; // Row Offset

    data[index * BITS + i + 0] = x;
    data[index * BITS + i + 1] = y;
    data[index * BITS + i + 2] = z;
  }
  /**
   * Create a new set of uniforms for describing the curve modifier
   *
   * @param { DataTexture } Texture which holds the curve description
   */


  function getUniforms(splineTexture) {
    var uniforms = {
      spineTexture: {
        value: splineTexture
      },
      pathOffset: {
        type: 'f',
        value: 0
      },
      // time of path curve
      pathSegment: {
        type: 'f',
        value: 1
      },
      // fractional length of path
      spineOffset: {
        type: 'f',
        value: 161
      },
      spineLength: {
        type: 'f',
        value: 400
      },
      flow: {
        type: 'i',
        value: 1
      }
    };
    return uniforms;
  }

  function modifyShader(material, uniforms) {
    var numberOfCurves = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
    if (material.__ok) return;
    material.__ok = true;

    material.onBeforeCompile = function (shader) {
      if (shader.__modified) return;
      shader.__modified = true;
      Object.assign(shader.uniforms, uniforms);
      var vertexShader = "\n\t\tuniform sampler2D spineTexture;\n\t\tuniform float pathOffset;\n\t\tuniform float pathSegment;\n\t\tuniform float spineOffset;\n\t\tuniform float spineLength;\n\t\tuniform int flow;\n\n\t\tfloat textureLayers = ".concat(TEXTURE_HEIGHT * numberOfCurves, ".;\n\t\tfloat textureStacks = ").concat(TEXTURE_HEIGHT / 4, ".;\n\n\t\t").concat(shader.vertexShader, "\n\t\t") // chunk import moved in front of modified shader below
      .replace('#include <beginnormal_vertex>', '') // vec3 transformedNormal declaration overriden below
      .replace('#include <defaultnormal_vertex>', '') // vec3 transformed declaration overriden below
      .replace('#include <begin_vertex>', '') // shader override
      .replace(/void\s*main\s*\(\)\s*\{/, "\nvoid main() {\n#include <beginnormal_vertex>\n\nvec4 worldPos = modelMatrix * vec4(position, 1.);\n\nbool bend = flow > 0;\nfloat xWeight = bend ? 0. : 1.;\n\n#ifdef USE_INSTANCING\nfloat pathOffsetFromInstanceMatrix = instanceMatrix[3][2];\nfloat spineLengthFromInstanceMatrix = instanceMatrix[3][0];\nfloat spinePortion = bend ? (worldPos.x + spineOffset) / spineLengthFromInstanceMatrix : 0.;\nfloat mt = (spinePortion * pathSegment + pathOffset + pathOffsetFromInstanceMatrix)*textureStacks;\n#else\nfloat spinePortion = bend ? (worldPos.x + spineOffset) / spineLength : 0.;\nfloat mt = (spinePortion * pathSegment + pathOffset)*textureStacks;\n#endif\n\nmt = mod(mt, textureStacks);\nfloat rowOffset = floor(mt);\n\n#ifdef USE_INSTANCING\nrowOffset += instanceMatrix[3][1] * ".concat(TEXTURE_HEIGHT, ".;\n#endif\n\nvec3 spinePos = texture2D(spineTexture, vec2(mt, (0. + rowOffset + 0.5) / textureLayers)).xyz;\nvec3 a =        texture2D(spineTexture, vec2(mt, (1. + rowOffset + 0.5) / textureLayers)).xyz;\nvec3 b =        texture2D(spineTexture, vec2(mt, (2. + rowOffset + 0.5) / textureLayers)).xyz;\nvec3 c =        texture2D(spineTexture, vec2(mt, (3. + rowOffset + 0.5) / textureLayers)).xyz;\nmat3 basis = mat3(a, b, c);\n\nvec3 transformed = basis\n\t* vec3(worldPos.x * xWeight, worldPos.y * 1., worldPos.z * 1.)\n\t+ spinePos;\n\nvec3 transformedNormal = normalMatrix * (basis * objectNormal);\n\t\t\t")).replace('#include <project_vertex>', "vec4 mvPosition = modelViewMatrix * vec4( transformed, 1.0 );\n\t\t\t\tgl_Position = projectionMatrix * mvPosition;");
      shader.vertexShader = vertexShader;
    };
  }
  /**
   * A helper class for making meshes bend aroudn curves
   */


  var Flow = /*#__PURE__*/function () {
    /**
     * @param {Mesh} mesh The mesh to clone and modify to bend around the curve
     * @param {number} numberOfCurves The amount of space that should preallocated for additional curves
     */
    function Flow(mesh) {
      var numberOfCurves = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;

      _classCallCheck(this, Flow);

      var obj3D = mesh.clone();
      var splineTexure = initSplineTexture(numberOfCurves);
      var uniforms = getUniforms(splineTexure);
      obj3D.traverse(function (child) {
        if (child instanceof _three.Mesh || child instanceof _three.InstancedMesh) {
          child.material = child.material.clone();
          modifyShader(child.material, uniforms, numberOfCurves);
        }
      });
      this.curveArray = new Array(numberOfCurves);
      this.curveLengthArray = new Array(numberOfCurves);
      this.object3D = obj3D;
      this.splineTexure = splineTexure;
      this.uniforms = uniforms;
    }

    _createClass(Flow, [{
      key: "updateCurve",
      value: function updateCurve(index, curve) {
        if (index >= this.curveArray.length) throw Error('Index out of range for Flow');
        var curveLength = curve.getLength();
        this.uniforms.spineLength.value = curveLength;
        this.curveLengthArray[index] = curveLength;
        this.curveArray[index] = curve;
        updateSplineTexture(this.splineTexure, curve, index);
      }
    }, {
      key: "moveAlongCurve",
      value: function moveAlongCurve(amount) {
        this.uniforms.pathOffset.value += amount;
      }
    }]);

    return Flow;
  }();

  _exports.Flow = Flow;
  var matrix = new _three.Matrix4();
  /**
   * A helper class for creating instanced versions of flow, where the instances are placed on the curve.
   */

  var InstancedFlow = /*#__PURE__*/function (_Flow) {
    _inherits(InstancedFlow, _Flow);

    var _super = _createSuper(InstancedFlow);

    /**
     *
     * @param {number} count The number of instanced elements
     * @param {number} curveCount The number of curves to preallocate for
     * @param {Geometry} geometry The geometry to use for the instanced mesh
     * @param {Material} material The material to use for the instanced mesh
     */
    function InstancedFlow(count, curveCount, geometry, material) {
      var _this;

      _classCallCheck(this, InstancedFlow);

      var mesh = new _three.InstancedMesh(geometry, material, count);
      mesh.instanceMatrix.setUsage(_three.DynamicDrawUsage);
      _this = _super.call(this, mesh, curveCount);
      _this.offsets = new Array(count).fill(0);
      _this.whichCurve = new Array(count).fill(0);
      return _this;
    }
    /**
     * The extra information about which curve and curve position is stored in the translation components of the matrix for the instanced objects
     * This writes that information to the matrix and marks it as needing update.
     *
     * @param {number} index of the instanced element to update
     */


    _createClass(InstancedFlow, [{
      key: "writeChanges",
      value: function writeChanges(index) {
        matrix.makeTranslation(this.curveLengthArray[this.whichCurve[index]], this.whichCurve[index], this.offsets[index]);
        this.object3D.setMatrixAt(index, matrix);
        this.object3D.instanceMatrix.needsUpdate = true;
      }
      /**
       * Move an individual element along the curve by a specific amount
       *
       * @param {number} index Which element to update
       * @param {number} offset Move by how much
       */

    }, {
      key: "moveIndividualAlongCurve",
      value: function moveIndividualAlongCurve(index, offset) {
        this.offsets[index] += offset;
        this.writeChanges(index);
      }
      /**
       * Select which curve to use for an element
       *
       * @param {number} index the index of the instanced element to update
       * @param {number} curveNo the index of the curve it should use
       */

    }, {
      key: "setCurve",
      value: function setCurve(index, curveNo) {
        if (isNaN(curveNo)) throw Error('curve index being set is Not a Number (NaN)');
        this.whichCurve[index] = curveNo;
        this.writeChanges(index);
      }
    }]);

    return InstancedFlow;
  }(Flow);

  _exports.InstancedFlow = InstancedFlow;
});