Parse

File Parse curve-extras.js

This tree is parsed live from the source file.

Classes

  • {{ item.name }}

    • {{ key }}

Not Classes

{{ getTree() }}

Comments

{{ getTreeComments() }}

Source

            /*
 */
// moved to functions/context.js
const saveRestoreDraw = function(ctx, position, callback) {
    /*
        + `save()` the context
        + translate and rotate to `position``
        + run the function
        + Restore.
     */
    ctx.save()

    let offsetX = -position.radius
        , offsetY = 0 // position.radius
    let tip = (new Point(offsetX, offsetY))

    ctx.translate(position.x, position.y) // Becomes the draw point.
    ctx.rotate(degToRad(position.rotation))// + this.tick))

    /* Draw tip */
    callback && callback(tip)
    // tip.draw.ngon(ctx, 3, position.radius)
    /* Pop the stack, de-rotating the page.*/
    ctx.translate(-position.x, -position.y) // Becomes the draw point.
    ctx.restore()

}


class Line {
    doTips = true
    doBeginPath = true
    doClosePath = true

    constructor(p1, p2, color='red', width=1){
        // new Line([90, 130], [200, 300], 420)
        this.create.apply(this, arguments)
    }

    get [0]() {
        return this.a
    }

    get [1]() {
        return this.b
    }

    last() {
        return this.b
    }

    first() {
        return this.a
    }

    get length(){
        return this.a.distanceTo(this.b)
    }

    get points() {
        return [this.a, this.b]
    }

    create(p1, p2, color='red', width=1) {
        this.a = point(p1)
        this.b = point(p2)
        this.color = color
        this.width = width
    }

    render(ctx, conf={}) {
        this.start(ctx, conf)
        this.draw(ctx, conf)
        this.close(ctx)
    }

    start(ctx) {
        this.doBeginPath && ctx.beginPath();
        let a = this.a;
        ctx.moveTo(a[0], a[1])
    }

    draw(ctx, color=undefined) {
        let conf = {color}
        if(typeof(color) != 'string') {
            conf = color
        }

        if(conf.color != undefined && this.color != undefined ) {
            ctx.strokeStyle = conf.color || this.color
        }


        if(conf.width != undefined) {
            ctx.lineWidth = conf.width == undefined? 1: conf.width
        }

        this.perform(ctx)
        this.writeLine(ctx)
        ;(this.doTips) && this.performDrawTips(ctx)
    }

    performDrawTips(ctx, result){
        this.performTipA(ctx, result)
        this.performTipB(ctx, result)
    }

    /* Apply a tip at point A, (the first position).

    The Tip is a point, with some styling with an ngon.
    */
    performTipA(ctx, result){
        let tipA = this.a
        ctx.beginPath()

        this.drawPolyTipA(ctx, result, tipA)
        this._ctx_fill(ctx)

        let point = this.drawLineTipA(ctx, result, tipA)
        this.writeLineTip(ctx, point)
    }

    writeLineTip(ctx, tip) {

        tip.rotation += 90
        tip.radius = 10

        let lineA = tip.project()
        tip.rotation += 180
        let lineB = tip.project()
        lineA.pen.line(ctx, lineB, 'orange', 2)
    }

    drawPolyTipA(ctx, result, position) {

        let tail = this.a.copy()
        /* Rotate this _end point_ to look directly at, then perform a 180,
        allowing Polypoint to offset the calculation */
        let target = this.b
        tail.lookAt(new Point(target))
        /* Spin the point around the center (its nose).
        rotate to be _under_ the point. */
        tail.rotation += 180
        /* poly distance from the tip */
        tail.radius = 10

        let callback = tip => {
            /* Draw tip */
            tip.draw.ngon(ctx, 3, tail.radius)
        }

        saveRestoreDraw(ctx, tail, callback)

        return tail
    }

    _ctx_fill(ctx) {
        // Make a fancy Fill thing.
        ctx.fillStyle = '#880000'
        ctx.fill()
    }

    drawLineTipA(ctx, result, position) {
        let s = this.a.copy()
        ctx.moveTo(s[0], s[1])

        let target = this.b.copy()
        s.lookAt(new Point(target))
        return s
    }

    performTipB(ctx, result){
        let tipB = this.b

        this.drawPolyTipB(ctx, result, tipB);
        this._ctx_fill(ctx)

        let res = this.drawLineTipB(ctx, result, tipB)
        this.writeLineTip(ctx, res)
    }


    drawPolyTipB(ctx, result, position) {
        let point = (new Point(position)).copy()// || this.b)
            /* First we grab the cached _curve_ of this line instance. */
            ;
        /* For the tail, we take the last-1 [and last] to render a
        point at this position. */
        let penUltP = this.b.copy()
            ;

        /*Ensure the given point is a point instance. */
        /* it's likely the original point, therefore we ensure it's new. */
        let tail = point.copy()
        /* Rotate this _end point_ to look directly at, then perform a 180, allowing Polypoint to offset the calculation */
        let target = this.a

        tail.lookAt(new Point(target))
        tail.rotation += 180

        tail.radius = 7

        // ctx.closePath()
        // ctx.stroke()
        ctx.beginPath()
        ctx.save()

        let offsetX = -tail.radius
            , offsetY = 0 // tail.radius
        let tip = (new Point(offsetX, offsetY))

        ctx.translate(tail.x, tail.y) // Becomes the draw point.
        ctx.rotate(degToRad(tail.rotation))// + this.tick))

        /* Draw tip */
        tip.draw.ngon(ctx, 3, tail.radius)
        /* Pop the stack, de-rotating the page.*/
        ctx.restore()

        return tail
    }

    drawLineTipB(ctx, result, position) {
        let s = new Point(position)
        ctx.moveTo(s[0], s[1])
        let tail = s.copy()
        /* Look at the target point. */

        let a = this.a
        // let penUltP = curves[curves.length-2]
        tail.lookAt(a)
        return tail
    }

    writeLine(ctx) {
        ctx.stroke()
    }

    perform(ctx) {
        let b = this.b;
        ctx.lineTo(b[0], b[1])
    }

    close(ctx) {
        this.doClosePath && ctx.closePath()
    }
}

Polypoint.head.install(Line)


class BezierCurve extends Line {

    // create(p1, p2, color='red', width=1) {
    // }
    useCache = false
    getControlPoints(useCache=this.useCache) {

        if(useCache === true && this.cachedControlPoints) {
            return this.cachedControlPoints
        }

        let a = this.a
          , b = this.b
          ;

        // let midDistance = a.distanceTo(b)*.5
        // let offset = this.offset == undefined? 0: this.offset

        /*A bezier requires two control points */
        // let cached = [
        //       a.project(midDistance + offset)
        //     , b.project(midDistance + offset)
        // ]
        let cached = [
              a.project()
            , b.project()
        ]

        this.cachedControlPoints = cached;
        return cached
    }

    get points() {
        return new PointList(this.a,this.b)
    }


    drawLineTipA(ctx, result, position) {
        let s = this.a.copy()
        ctx.moveTo(s[0], s[1])
        s.rotation = this.a.rotation + 180
        // let target = this.b.copy()
        // s.lookAt(new Point(target))
        return s
    }

    drawPolyTipA(ctx, result, position) {

        let tail = this.a.copy()
        /* Rotate this _end point_ to look directly at, then perform a 180,
        allowing Polypoint to offset the calculation */
        let target = this.b
        // tail.lookAt(new Point(target))
        /* Spin the point around the center (its nose).
        rotate to be _under_ the point. */
        tail.rotation = this.a.rotation + 180
        /* poly distance from the tip */
        tail.radius = 7

        let callback = tip => {
            /* Draw tip */
            tip.draw.ngon(ctx, 3, tail.radius)
        }

        saveRestoreDraw(ctx, tail, callback)

        return tail
    }

    drawLineTipB(ctx, result, position) {
        let s = new Point(position).copy()
        ctx.moveTo(s[0], s[1])
        // let tail = s.copy()
        /* Look at the target point. */
        s.rotation = this.b.rotation
        let a = this.a
        // let penUltP = curves[curves.length-2]
        // tail.lookAt(a)
        return s
    }

    drawPolyTipB(ctx, result, position) {
        /* it's likely the original point, therefore we ensure it's new. */
        let tail = (new Point(position)).copy()// || this.b)
            ;

        /* TODO:
        Attempt to understand why the b tip of a spline is -90,
        But the `a` tip 180*/
        tail.rotation = this.b.rotation + 180
        tail.radius = 7

        let callback = tip => {
            /* Draw tip */
            tip.draw.ngon(ctx, 3, tail.radius)
        }

        saveRestoreDraw(ctx, tail, callback)
    }

    // render(ctx, conf={}) {
    //     this.start(ctx, conf)
    //     this.draw(ctx, conf)
    //     this.close(ctx)
    // }

    // draw(ctx, color=undefined) {
    //     // ...
    //     this.perform(ctx)
    //     this.writeLine(ctx)
    //     ;(this.doTips) && this.performDrawTips(ctx)
    // }

    perform(ctx) {
        let b = this.b;
        let cps = this.getControlPoints()
        // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/bezierCurveTo
        let bp = b
        ctx.bezierCurveTo(cps[0].x, cps[0].y, cps[1].x, cps[1].y, bp.x, bp.y)
    }
}



class QuadraticCurve extends Line {
    /* A Quad line control point is A.project() */
    useCache = false
    getControlPoints(useCache=this.useCache) {

        if(useCache === true && this.cachedControlPoints) {
            return this.cachedControlPoints
        }

        let a = this.a
          , b = this.b
          ;

        // let midDistance = a.distanceTo(b)*.5
        // let offset = this.offset == undefined? 0: this.offset

        /*A quadratic curve requires two control points */
        let cached = [
              a.project()
            // , b.project()
        ]

        this.cachedControlPoints = cached;
        return cached
    }

    get points() {
        return new PointList(this.a,this.b)
    }

    perform(ctx) {
        let b = this.b;
        let cps = this.getControlPoints()
        // https://developer.mozilla.org/
        // en-US/docs/Web/API/CanvasRenderingContext2D/quadraticCurveTo
        ctx.quadraticCurveTo(cps[0].x, cps[0].y, b.x, b.y);
    }
}


Polypoint.head.install(BezierCurve)


class CantenaryCurve extends Line {

    doTips = true
    sine = 1
    bounceRate = .1
    _oneReducer = 1
    stable = false
    /* When sine bouncing, reduce over time to stop the motion.
    0 would turn of motion, 1 would bounce forever very quickly.*/
    reductionRate = .999
    swingDegrees = 20
    elasticity = .09

    create(a,b, length=undefined, color='red', width=1) {
        super.create(a,b,color,  width)
        this._length = length
        /* Length elasticity when oscillating */
        this.stretchMultiplier = 1
    }

    get points() {
        return new PointList(this.a,this.b)
    }

    set length(v) {
        this._length = v
        this.cachedCantenary = undefined
    }
    restart() {
        this.sine = this._restLength * this.stretchMultiplier
        this._oneReducer = 1
        this.stable = false;
    }

    set restLength(v) {
        this._restLength = v
        this.sine = v * this.stretchMultiplier
        this._oneReducer = 1
    }

    set stretchMultiplier(v) {
        this.sine = this._restLength * v
    }

    getCurveLength(a, b) {
        let l = this._length
        if(l == undefined){
            return distance(a, b) * 1.5
        }
        return l
    }

    getControlPoints() {
        return this.getCachedCurve()
    }

    getCachedCurve() {
        if(!this.cachedCantenary ) {
            let a = this.a
                , b = this.b
                ;
            this.cachedCantenary = getCatenaryCurve(a,b, this.getCurveLength(a,b))
        }

        return this.cachedCantenary
    }

    draw(ctx, color=undefined) {
        ctx.strokeStyle = color == undefined? this.color: color
        ctx.lineWidth = this.width == undefined? 1: this.width
        let pl = new PointList()
        pl.push(this.a, this.b)

        let result = this.perform(ctx)
        this.writeLine(ctx)
        ;(this.doTips) && this.performDrawTips(ctx, result)
    }

    perform(ctx, result) {

        result = result || this.getControlPoints()
        let curves = result.curves


        ctx.moveTo(result.start[0], result.start[1])

        if(!curves) {
            let p = result.lines[0]
            ctx.lineTo(p[0], p[1])
            return result;
        }

        for (let i = 0; i < curves.length; i++) {
            let c = curves[i]
            ctx.quadraticCurveTo(
                c[0], // cpx
                c[1], // cpy
                c[2], // x
                c[3], // y
            )
        }

        return result;
    }

    // performDrawTips(ctx, result){
    //     this.performTipA(ctx, result)
    //     this.performTipB(ctx, result)
    // }

    performTipB(ctx, result){
        let tipB = this.b

        this.drawPolyTipB(ctx, result, tipB);
        this.writePolyTipB(ctx)

        let res = this.drawLineTipB(ctx, result, tipB)
        // this.writeLineTipB(ctx, res)
        this.writeLineTip(ctx, res)
    }

    drawLineTipB(ctx, result, position) {
        let s = new Point(position)
            /* First we grab the cached _curve_ of this line instance. */
            , curves = result.curves

        ctx.moveTo(s[0], s[1])

        let tail = s.copy()
        /* Look at the target point. */
        let lines = result.lines;
        let penUltP =(curves == undefined)? result.start: curves[curves.length-1]
        // let penUltP = curves[curves.length-2]
        tail.lookAt(new Point(penUltP))
        return tail
    }

    writeLineTipB(ctx, tip) {

        tip.rotation += 90
        tip.radius = 10

        let lineA = tip.project()
        tip.rotation += 180
        let lineB = tip.project()
        lineA.pen.line(ctx, lineB, '#AADDFF', 2)
    }

    // performTipA(ctx, result){
    //     let tipA = this.a
    //     ctx.beginPath()
    //     this.drawPolyTipA(ctx, result, tipA)
    //     this.writePolyTipB(ctx)

    //     let res = this.drawLineTipA(ctx, result, tipA)
    //     this.writeLineTip(ctx, res)
    // }

    drawTipA(ctx, result, position) {
    }

    drawPolyTipA(ctx, result, position) {
        /* First we grab the cached _curve_ of this line instance. */
        let curves = result.curves
        /* For the tail, we take the last-1 [and last] to render a
        point at this position. */
        let lines = result.lines
        let penUltP = (curves == undefined)? this.b: curves[1]

        /*Ensure the given point is a point instance. */
        /* it's likely the original point, therefore we ensure it's new. */
        let tail = (new Point(position || result.start)).copy()
        /* Rotate this _end point_ to look directly at, then perform a 180,
        allowing Polypoint to offset the calculation */
        let target = curves? penUltP: this.b
        tail.lookAt(new Point(target))
        /* Spin the point around the center (its nose).
        rotate to be _under_ the point. */
        tail.rotation += 180
        /* poly distance from the tip */
        tail.radius = 6

        let callback = tip => {
            /* Draw tip */
            tip.draw.ngon(ctx, 3, tail.radius)
        }

        saveRestoreDraw(ctx, tail, callback)

        return tail
    }

    drawLineTipA(ctx, result, position) {
        let s = new Point(position || result.start)
            /* First we grab the cached _curve_ of this line instance. */
            , curves = result.curves

        ctx.moveTo(s[0], s[1])

        /* The position of the tip, picking nodes along the line */
        let target = (curves == undefined)? result.lines[0]: curves[0];
        let tail = (new Point(s)).copy()
        /* Look at the target point. */
        tail.lookAt(new Point(target))
        return tail
    }

    drawTipB(ctx, result, position) {
        return this.drawLineTipB(ctx, result, position)
        // return this.drawPolyTipB(ctx, result, position)
    }

    drawPolyTipB(ctx, result, position) {
        let point = (new Point(position)).copy()// || this.b)
            /* First we grab the cached _curve_ of this line instance. */
            , curves = result.curves
            ;
        /* For the tail, we take the last-1 [and last] to render a
        point at this position. */
        let lines = result.lines
        let penUltP =(curves == undefined)? lines[lines.length-1]: curves[curves.length-2]
            ;

        /*Ensure the given point is a point instance. */
        /* it's likely the original point, therefore we ensure it's new. */
        let tail = point.copy()
        /* Rotate this _end point_ to look directly at, then perform a 180, allowing Polypoint to offset the calculation */
        let target = curves? penUltP: result.start

        tail.lookAt(new Point(target))
        tail.rotation += 180

        tail.radius = 7

        // ctx.closePath()
        // ctx.stroke()
        ctx.beginPath()
        ctx.save()

        let offsetX = -tail.radius
            , offsetY = 0 // tail.radius
        let tip = (new Point(offsetX, offsetY))

        ctx.translate(tail.x, tail.y) // Becomes the draw point.
        ctx.rotate(degToRad(tail.rotation))// + this.tick))

        /* Draw tip */
        tip.draw.ngon(ctx, 3, tail.radius)
        /* Pop the stack, de-rotating the page.*/
        ctx.restore()

        return tail
    }

    writeLineTip(ctx, tip) {

        tip.rotation += 90
        tip.radius = 10

        let lineA = tip.project()
        tip.rotation += 180
        let lineB = tip.project()
        lineA.pen.line(ctx, lineB, 'orange', 2)

    }

    writePolyTipB(ctx) {
        // Make a fancy Fill thing.
        ctx.fillStyle = '#880000'
        ctx.fill()
    }

    update(ctx, tick) {

        if(this.stable = this.sine < .1) {
            return
        }

        this.stretchMultiplier = this.elasticity * this._oneReducer

        this.sine *= this.reductionRate
        this._oneReducer *= this.reductionRate

        this.length = this._restLength + (
                Math.sin(tick * this.bounceRate) * this.sine
            )
    }

    updateSwing(ctx, tick) {

        let oneRed = this._oneReducer
        /* How much _left and right_ can the line swing. */
        let swingDegrees = this.swingDegrees * oneRed
        /* How much theta for a gravity vector.
            configured to be 0 as naturally _down_ gravity.
            -45 would swing south west. */
        let gravityDegrees = 0
        let swingSpeed = this.bounceRate * .5 // .04
        /* Default .1, changed here to match gravity swing */
        this.stretchMultiplier = this.elasticity * oneRed

        let deltaSwing = (Math.sin((tick * swingSpeed) % 360) * swingDegrees)
        let t = -90 + gravityDegrees + deltaSwing
        this.deltaT = t
        this.useCache = false
        let ps = this.points
        let com = ps.centerOfMass()
        // ps.offset(com.multiply(-1))
        // ps.rotate(1)
        this.a.x = ps[0].x
        this.a.y = ps[0].y
        this.b.x = ps[1].x
        this.b.y = ps[1].y

        this.color = (this.stable)? 'green': 'red'
    }

    clear() {
        this.cachedCantenary = undefined
    }

}


class CatenaryCurve extends CantenaryCurve {
}
copy