modeling/src/curves/bezier/arcLengthToT.js

const lengths = require('./lengths')

/**
 * Convert a given arc length along a bezier curve to a t value.
 * Useful for generating equally spaced points along a bezier curve.
 *
 * @example
 * const points = [];
 * const segments = 9; // this will generate 10 equally spaced points
 * const increment = bezier.length(100, bezierCurve) / segments;
 * for(let i = 0; i <= segments; i++) {
 *   const t = bezier.arcLengthToT({distance: i * increment}, bezierCurve);
 *   const point = bezier.valueAt(t, bezierCurve);
 *   points.push(point);
 * }
 * return points;
 *
 * @param {Object} [options] options for construction
 * @param {Number} [options.distance=0] the distance along the bezier curve for which we want to find the corresponding t value.
 * @param {Number} [options.segments=100] the number of segments to use when approximating the curve length.
 * @param {Object} bezier a bezier curve.
 * @returns a number in the [0, 1] interval or NaN if the arcLength is negative or greater than the total length of the curve.
 * @alias module:modeling/curves/bezier.arcLengthToT
 */
const arcLengthToT = (options, bezier) => {
  const defaults = {
    distance: 0,
    segments: 100
  }
  const { distance, segments } = Object.assign({}, defaults, options)

  const arcLengths = lengths(segments, bezier)
  // binary search for the index with largest value smaller than target arcLength
  let startIndex = 0
  let endIndex = segments
  while (startIndex <= endIndex) {
    const middleIndex = Math.floor(startIndex + (endIndex - startIndex) / 2)
    const diff = arcLengths[middleIndex] - distance
    if (diff < 0) {
      startIndex = middleIndex + 1
    } else if (diff > 0) {
      endIndex = middleIndex - 1
    } else {
      endIndex = middleIndex
      break
    }
  }
  // if we have an exact match, return it
  const targetIndex = endIndex
  if (arcLengths[targetIndex] === distance) {
    return targetIndex / segments
  }
  // we could get finer grain at lengths, or use simple interpolation between two points
  const lengthBefore = arcLengths[targetIndex]
  const lengthAfter = arcLengths[targetIndex + 1]
  const segmentLength = lengthAfter - lengthBefore
  // determine where we are between the 'before' and 'after' points
  const segmentFraction = (distance - lengthBefore) / segmentLength
  // add that fractional amount and return
  return (targetIndex + segmentFraction) / segments
}

module.exports = arcLengthToT