Parse

File Parse catenary-curve.js

This tree is parsed live from the source file.

Classes

  • {{ item.name }}

    • {{ key }}

Not Classes

{{ getTree() }}

Comments

{{ getTreeComments() }}

Source

            /**
 * Given two points and a length, calculate and draw the catenary.
 *
 * TypeScript implementation:
 * Copyright (c) 2018, 2023 Jan Hug <me@dulnan.net>
 * Released under the MIT license.
 *
 * ----------------------------------------------------------------------------
 *
 * Original ActionScript implementation:
 * Copyright poiasd ( http://wonderfl.net/user/poiasd )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/8Bnl
 *
 * ----------------------------------------------------------------------------
 *
 * Archived by and downloaded from:
 * http://wa.zozuar.org/code.php?c=8Bnl
 */

const EPSILON = 1e-6

//
// https://trimaran-san.de/en/die-kettenkurve-oder-wie-ein-mathematiker-ankert/
//
//
/**
 * Calculate the catenary curve.
 * Increasing the segments value will produce a catenary closer
 * to reality, but will require more calcluations.
 */
// The catenary parameter.
// First point.
// Second point.
// The calculated offset on the x axis.
// The calculated offset on the y axis.
// How many "parts" the chain should be made of.
function getCurve(a, p1, p2, offsetX, offsetY, segments) {

    const data = [
        // Calculate the first point on the curve
        [p1.x, standardCosH(a, p1.x, offsetX, offsetY)]
    ]

    const d = p2.x - p1.x
    const length = segments - 1

    // Calculate the points in between the first and last point
    for (let i = 0; i < length; i++) {
        const x = p1.x + (d * (i + 0.5)) / length
        const y = standardCosH(a,x, offsetX, offsetY)
        data.push([x, y])
    }

    // Calculate the last point on the curve
    data.push([p2.x, standardCosH(a, p2.x, offsetX, offsetY)])

    return data
}

var superVal = 29

const standardCosH = function(a, x, offsetX, offsetY) {
    return a * Math.cosh((x - offsetX) / a) + offsetY
}


/**
 * Draws a straight line between two points.
 *
 */
function getLineResult(data) {
  return {
    type: "line",
    start: data[0],
    lines: data.slice(1)
  }
}

/**
 * Determines catenary parameter.
 *
 */
function getCatenaryParameter(h, v, length, limit) {
    const m = Math.sqrt(length * length - v * v) / h
    let x = Math.acosh(m) + 1
    let prevx = -1
    let count = 0

    // Iterate until we find a suitable catenary parameter or reach the iteration
    // limit
    while (Math.abs(x - prevx) > EPSILON && count < limit) {
      prevx = x
      x = x - (Math.sinh(x) - m * x) / (Math.cosh(x) - m)
      count++
    }

    return h / (2 * x)
}

/**
 * Draws a quadratic curve between every calculated catenary segment,
 * so that the segments don't look like straight lines.
 */
function getCurveResult(data) {
  let length = data.length - 1
  let ox = data[1][0]
  let oy = data[1][1]

  const start = [data[0][0], data[0][1]]
  const curves = []

  for (let i = 2; i < length; i++) {
    const x = data[i][0]
    const y = data[i][1]
    const mx = (x + ox) * 0.5
    const my = (y + oy) * 0.5
    curves.push([ox, oy, mx, my])
    ox = x
    oy = y
  }

  length = data.length
  curves.push([
    data[length - 2][0],
    data[length - 2][1],
    data[length - 1][0],
    data[length - 1][1]
  ])

  return { type: "quadraticCurve", start, curves }
}

/**
 * Pass in the return value from getCatenaryCurve and your canvas context to
 * draw the curve.
 */
function drawResult(result, context) {
  if (result.type === "quadraticCurve") {
    drawResultCurve(result, context)
  } else if (result.type === "line") {
    drawResultLine(result, context)
  }
}

/**
 * Draw the curve using lineTo.
 */
function drawResultLine(result, context) {
  context.moveTo(...result.start)
  for (let i = 0; i < result.lines.length; i++) {
    context.lineTo(...result.lines[i])
  }
}

/**
 * Draw the curve using quadraticCurveTo.
 */
function drawResultCurve(result, context) {
  context.moveTo(...result.start)

  for (let i = 0; i < result.curves.length; i++) {
    context.quadraticCurveTo(...result.curves[i])
  }
}

/**
 * Get the difference for x and y axis to another point
 */
function getDifferenceTo(p1, p2) {
  return { x: p1.x - p2.x, y: p1.y - p2.y }
}

/**
 * Return the distance in pixels between two points.
 */
function getDistanceBetweenPoints(p1, p2) {
  const diff = getDifferenceTo(p1, p2)

  return Math.sqrt(Math.pow(diff.x, 2) + Math.pow(diff.y, 2))
}

/**
 * Approximates the catenary curve between two points and returns the resulting
 * coordinates.
 *
 * If the curve would result in a single straight line, the approximation is
 * skipped and the input coordinates are returned.
 *
 * It returns an object with a property `type` to differenciate between `line`
 * and `quadraticCurve`. You can pass this object together with your 2D canvas
 * context to `drawResult` to directly draw it to the canvas.
 */
function getCatenaryCurve(point1, point2, chainLength, options = {}) {
    const segments = options.segments || 25
    const iterationLimit = options.iterationLimit || 6

    // The curves are reversed
    const isFlipped = point1.x > point2.x

    const p1 = isFlipped ? point2 : point1
    const p2 = isFlipped ? point1 : point2

    const distance = getDistanceBetweenPoints(p1, p2)

    if(distance < chainLength) {
        const diff = p2.x - p1.x

        if (diff > 0.01) {
            const h = p2.x - p1.x
            const v = p2.y - p1.y

            const a = -getCatenaryParameter(h, v, chainLength, iterationLimit)
            const x = (a * Math.log((chainLength + v) / (chainLength - v)) - h) * 0.5
            const y = a * Math.cosh(x / a)

            const offsetX = p1.x - x
            const offsetY = p1.y - y
            const curveData = getCurve(a, p1, p2, offsetX, offsetY, segments)
            if (isFlipped) {
                curveData.reverse()
            }
            return getCurveResult(curveData)
        }

        const mx = (p1.x + p2.x) * 0.5
        const my = (p1.y + p2.y + chainLength) * 0.5

        return getLineResult([
            [p1.x, p1.y],
            [mx, my],
            [p2.x, p2.y]
        ])
    }

    return getLineResult([
        [p1.x, p1.y],
        [p2.x, p2.y]
    ])
}

copy