(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; });