• Jump To … +
    ./source/asset-management/image-asset.js ./source/asset-management/noise-asset.js ./source/asset-management/raw-asset.js ./source/asset-management/reaction-diffusion-asset.js ./source/asset-management/sprite-asset.js ./source/asset-management/video-asset.js ./source/core/animation-loop.js ./source/core/display-cycle.js ./source/core/document.js ./source/core/events.js ./source/core/init.js ./source/core/library.js ./source/core/snippets.js ./source/core/user-interaction.js ./source/factory/action.js ./source/factory/anchor.js ./source/factory/animation.js ./source/factory/bezier.js ./source/factory/block.js ./source/factory/button.js ./source/factory/canvas.js ./source/factory/cell.js ./source/factory/cog.js ./source/factory/color.js ./source/factory/conic-gradient.js ./source/factory/crescent.js ./source/factory/element.js ./source/factory/emitter.js ./source/factory/enhanced-label.js ./source/factory/filter.js ./source/factory/gradient.js ./source/factory/grid.js ./source/factory/group.js ./source/factory/label.js ./source/factory/line-spiral.js ./source/factory/line.js ./source/factory/loom.js ./source/factory/mesh.js ./source/factory/net.js ./source/factory/oval.js ./source/factory/particle-force.js ./source/factory/particle-spring.js ./source/factory/particle-world.js ./source/factory/particle.js ./source/factory/pattern.js ./source/factory/picture.js ./source/factory/polygon.js ./source/factory/polyline.js ./source/factory/quadratic.js ./source/factory/radial-gradient.js ./source/factory/rectangle.js ./source/factory/render-animation.js ./source/factory/shape.js ./source/factory/spiral.js ./source/factory/stack.js ./source/factory/star.js ./source/factory/tetragon.js ./source/factory/ticker.js ./source/factory/tracer.js ./source/factory/tween.js ./source/factory/unstacked-element.js ./source/factory/wheel.js ./source/helper/array-pool.js ./source/helper/color-engine.js ./source/helper/document-root-elements.js ./source/helper/filter-engine-bluenoise-data.js ./source/helper/filter-engine.js ./source/helper/random-seed.js ./source/helper/shape-path-calculation.js ./source/helper/shared-vars.js ./source/helper/system-flags.js ./source/helper/utilities.js ./source/helper/workstore.js ./source/mixin/anchor.js ./source/mixin/asset-advanced-functionality.js ./source/mixin/asset-consumer.js ./source/mixin/asset.js ./source/mixin/base.js ./source/mixin/button.js ./source/mixin/cascade.js ./source/mixin/cell-key-functions.js ./source/mixin/delta.js ./source/mixin/display-shape.js ./source/mixin/dom.js ./source/mixin/entity.js ./source/mixin/filter.js ./source/mixin/hidden-dom-elements.js ./source/mixin/mimic.js ./source/mixin/path.js ./source/mixin/pattern.js ./source/mixin/pivot.js ./source/mixin/position.js ./source/mixin/shape-basic.js ./source/mixin/shape-curve.js ./source/mixin/styles.js ./source/mixin/text.js ./source/mixin/tween.js ./source/scrawl.js ./source/untracked-factory/cell-fragment.js ./source/untracked-factory/coordinate.js ./source/untracked-factory/drag-zone.js ./source/untracked-factory/keyboard-zone.js ./source/untracked-factory/observe-update.js ./source/untracked-factory/palette.js ./source/untracked-factory/particle-history.js ./source/untracked-factory/quaternion.js ./source/untracked-factory/state.js ./source/untracked-factory/text-style.js ./source/untracked-factory/vector.js
  • §

    Shape-curve mixin

    This mixin defines additional attributes and functionality used byScrawl-canvas line, quadratic and bezier path-defined entitys.

    These entitys have additional positioning coordinates (startControl and endControl for Bezier entitys; control for Quadratic entitys; all three entity types use the end coordinate) which we use to construct and position them. Other artefacts can pivot themselves to these coordinates, and path-defined entitys can pivot the coordinates to other artefacts.

  • §

    Imports

    import { artefact, particle } from '../core/library.js';
    
    import { addStrings, isa_boolean, mergeOver, pushUnique, removeItem, Ωempty } from '../helper/utilities.js';
    
    import { makeCoordinate } from '../untracked-factory/coordinate.js';
  • §

    Shared constants

    import { _values, BEZIER, CONTROL, COORD, END, END_CONTROL, LINEAR, MOUSE, PARTICLE, PATH, PIVOT, QUADRATIC, START_CONTROL, T_BEZIER, T_ENHANCED_LABEL, T_LINE, T_PARTICLE, T_QUADRATIC, ZERO_STR } from '../helper/shared-vars.js';
  • §

    Local constants

    const END_PARTICLE = 'endParticle',
        END_PATH = 'endPath',
        END_PIVOT = 'endPivot',
        T_PATH = 'Path',
        T_PIVOT = 'Pivot';
    
    const capitalize = (s) => {
    
        if (!s.substring) return ZERO_STR;
        return s.charAt(0).toUpperCase() + s.slice(1);
    };
  • §

    Export function

    export default function (P = Ωempty) {
  • §

    Shared attributes

        const defaultAttributes = {
  • §

    The end coordinate (‘pin’) defines where a curve terminates.

    • Similar to the start coordinate, the end coordinate can be updated using the pseudo-attributes endX and endY.
            end: null,
  • §

    endPivot, endPivotCorner, addEndPivotHandle, addEndPivotOffset

    • Like the start coordinate, the end coordinate can be pivoted to another artefact. These attributes are used in the same way as the pivot, ‘pivotCorner’, addPivotHandle and addPivotOffset attributes.
            endPivot: ZERO_STR,
            endPivotCorner: ZERO_STR,
            endPivotIndex: -1,
            addEndPivotHandle: false,
            addEndPivotOffset: false,
  • §

    endPath, endPathPosition, addEndPathHandle, addEndPathOffset

    • Like the start coordinate, the end coordinate can be pathed to another artefact. These attributes are used in the same way as the path, ‘pathPosition’, addPathHandle and addPathOffset attributes.
            endPath: ZERO_STR,
            endPathPosition: 0,
            addEndPathHandle: false,
            addEndPathOffset: true,
  • §

    endParticle - attribute to store any particle the artefact mey be using for its position reference

            endParticle: ZERO_STR,
  • §

    endLockTo

    • Like the start coordinate, the end coordinate can swap between using absolute and relative positioning by setting this attribute. Accepted values are: coord (default, for absolute positioning), pivot, path, position, mouse.
    • The end coordinate does not support ‘mimic’ relative positioning.
    • The end lock does not support setting the x and y coordinates separately - its value is a string argument, not an [x, y] array!
            endLockTo: ZERO_STR,
  • §

    useStartAsControlPoint - by default, updating the curve entity’s start coordinate will move the entire curve. In situations where we need to treat the start coordinate like a curve ‘pin’, set this attribute to true.

            useStartAsControlPoint: false,
        };
        P.defs = mergeOver(P.defs, defaultAttributes);
  • §

    Packet management

        P.packetExclusions = pushUnique(P.packetExclusions, ['controlledLineOffset']);
        P.packetCoordinates = pushUnique(P.packetCoordinates, ['end']);
        P.packetObjects = pushUnique(P.packetObjects, ['endPivot', 'endPath']);
  • §

    Clone management

    No additional clone functionality defined here

  • §

    Kill management

    P.factoryKill = function () {
    
        _values(artefact).forEach(art => {
    
            if (art.name !== this.name) {
    
                if (art.startControlPivot && art.startControlPivot.name === this.name) art.set({ startControlPivot: false});
                if (art.controlPivot && art.controlPivot.name === this.name) art.set({ controlPivot: false});
                if (art.endControlPivot && art.endControlPivot.name === this.name) art.set({ endControlPivot: false});
                if (art.endPivot && art.endPivot.name === this.name) art.set({ endPivot: false});
    
                if (art.startControlPath && art.startControlPath.name === this.name) art.set({ startControlPath: false});
                if (art.controlPath && art.controlPath.name === this.name) art.set({ controlPath: false});
                if (art.endControlPath && art.endControlPath.name === this.name) art.set({ endControlPath: false});
                if (art.endPath && art.endPath.name === this.name) art.set({ endPath: false});
            }
        });
    };
  • §

    Get, Set, deltaSet

        const G = P.getters,
            S = P.setters,
            D = P.deltaSetters;
  • §

    useStartAsControlPoint

        S.useStartAsControlPoint = function (item) {
    
            this.useStartAsControlPoint = item;
    
            if (!item) {
    
                const controlledLine = this.controlledLineOffset;
                controlledLine[0] = 0;
                controlledLine[1] = 0;
            }
    
            this.updateDirty();
        };
  • §

    endPivot

        S.endPivot = function (item) {
    
            this.setControlHelper(item, END_PIVOT, END);
            this.updateDirty();
            this.dirtyEnd = true;
        };
  • §

    endParticle

        S.endParticle = function (item) {
    
            this.setControlHelper(item, END_PARTICLE, END);
            this.updateDirty();
            this.dirtyEnd = true;
        };
  • §

    endPath

        S.endPath = function (item) {
    
            this.setControlHelper(item, END_PATH, END);
            this.updateDirty();
            this.dirtyEnd = true;
        };
  • §

    endPathPosition

        S.endPathPosition = function (item) {
    
            this.endPathPosition = item;
            this.dirtyEnd = true;
            this.currentEndPathData = false;
            this.dirtyFilterIdentifier = true;
        };
        D.endPathPosition = function (item) {
    
            this.endPathPosition += item;
            this.dirtyEnd = true;
            this.currentEndPathData = false;
            this.dirtyFilterIdentifier = true;
        };
  • §

    end

    • pseudo-attributes endX, endY
        G.endPositionX = function () {
    
            return this.currentEnd[0];
        };
        G.endPositionY = function () {
    
            return this.currentEnd[1];
        };
        G.endPosition = function () {
    
            return [].concat(this.currentEnd);
        };
    
        S.endX = function (coord) {
    
            if (coord != null) {
    
                this.end[0] = coord;
                this.updateDirty();
                this.dirtyEnd = true;
                this.currentEndPathData = false;
            }
        };
        S.endY = function (coord) {
    
            if (coord != null) {
    
                this.end[1] = coord;
                this.updateDirty();
                this.dirtyEnd = true;
                this.currentEndPathData = false;
            }
        };
        S.end = function (x, y) {
    
            this.setCoordinateHelper(END, x, y);
            this.updateDirty();
            this.dirtyEnd = true;
            this.currentEndPathData = false;
        };
        D.endX = function (coord) {
    
            const c = this.end;
            c[0] = addStrings(c[0], coord);
            this.updateDirty();
            this.dirtyEnd = true;
            this.currentEndPathData = false;
        };
        D.endY = function (coord) {
    
            const c = this.end;
            c[1] = addStrings(c[1], coord);
            this.updateDirty();
            this.dirtyEnd = true;
            this.currentEndPathData = false;
        };
        D.end = function (x, y) {
    
            this.setDeltaCoordinateHelper(END, x, y);
            this.updateDirty();
            this.dirtyEnd = true;
            this.currentEndPathData = false;
        };
  • §

    endLockTo

        S.endLockTo = function (item) {
    
            this.endLockTo = item;
            this.updateDirty();
            this.dirtyEndLock = true;
            this.currentEndPathData = false;
        };
  • §

    Prototype functions

  • §

    curveInit - internal constructor helper function

        P.curveInit = function () {
    
            this.end = makeCoordinate();
            this.currentEnd = makeCoordinate();
            this.endLockTo = COORD;
            this.dirtyEnd = true;
    
            this.dirtyPins = [];
    
            this.controlledLineOffset = makeCoordinate();
        };
  • §

    setControlHelper - internal setter helper function

        P.setControlHelper = function (item, attr, label) {
    
            if (isa_boolean(item) && !item) {
    
                this[attr] = null;
    
                if (label === START_CONTROL) this.dirtyStartControlLock = true;
                else if (label === CONTROL) this.dirtyControlLock = true;
                else if (label === END_CONTROL) this.dirtyEndControlLock = true;
                else this.dirtyEndLock = true;
            }
            else if (item) {
    
                const oldControl = this[attr];
    
                let newControl = (item.substring) ? artefact[item] : item;
    
                if (attr.includes(T_PIVOT)) {
    
                    if (newControl && newControl.isArtefact) {
    
                        if (oldControl && oldControl.isArtefact) removeItem(oldControl.pivoted, this.name);
                        pushUnique(newControl.pivoted, this.name);
                        this[attr] = newControl;
                    }
                }
    
                else if (attr.includes(T_PATH)) {
    
                    if (newControl && newControl.isArtefact) {
    
                        if (oldControl && oldControl.isArtefact) removeItem(oldControl.pathed, this.name);
                        pushUnique(newControl.pathed, this.name);
                        this[attr] = newControl;
                    }
                }
    
                else if (attr.includes(T_PARTICLE)) {
    
                    newControl = (item.substring) ? particle[item] : item;
  • §

    For particles, we only care in cases where the particle has not yet been generated

    • Net entitys only create their particles at the time of their first Display cycle stamp operation, which will often be after the curve entity (line, quadratic, bezier) has been defined and created.
                    if (!newControl) {
    
                        this.updateDirty();
    
                        if (label === START_CONTROL) this.dirtyStartControl = true;
                        else if (label === CONTROL) this.dirtyControl = true;
                        else if (label === END_CONTROL) this.dirtyEndControl = true;
                        else this.dirtyEnd = true;
    
                        this[attr] = item;
                    }
                }
            }
        };
  • §

    buildPathPositionObject - internal function called by getPathPositionData

        P.buildPathPositionObject = function (unit, myLen) {
    
            if (unit) {
    
                const [unitSpecies, ...vars] = unit;
    
                let myPoint, angle;
    
                switch (unitSpecies) {
    
                    case LINEAR :
                        myPoint = this.positionPointOnPath(this.getLinearXY(myLen, ...vars));
                        angle = this.getLinearAngle(myLen, ...vars);
                        break;
    
                    case QUADRATIC :
                        myPoint = this.positionPointOnPath(this.getQuadraticXY(myLen, ...vars));
                        angle = this.getQuadraticAngle(myLen, ...vars);
                        break;
    
                    case BEZIER :
                        myPoint = this.positionPointOnPath(this.getBezierXY(myLen, ...vars));
                        angle = this.getBezierAngle(myLen, ...vars);
                        break;
                }
    
                let flipAngle = 0
                if (this.flipReverse) flipAngle++;
                if (this.flipUpend) flipAngle++;
    
                if (flipAngle === 1) angle = -angle;
    
                angle += this.roll;
    
                const lineOffset = this.controlledLineOffset;
    
                myPoint.x += lineOffset[0];
                myPoint.y += lineOffset[1];
    
                myPoint.angle = angle;
    
                return myPoint;
            }
            return false;
        };
  • §

    Display cycle functionality

  • §

    prepareStamp - the purpose of most of these actions is described in the entity mixin function that this function overwrites

        P.prepareStamp = function() {
    
            if (this.dirtyHost) this.dirtyHost = false;
  • §

    preparePinsForStamp function defined in line, quadratic and bezier modules

            if (this.dirtyPins.length) {
    
                this.preparePinsForStamp();
            }
  • §

    dirtyLock flags (one for each control) - trigger cleanLock functions - which in turn set appropriate dirty flags on the entity.

            if (this.dirtyLock) this.cleanLock();
            if (this.dirtyStartControlLock) this.cleanControlLock(START_CONTROL);
            if (this.dirtyEndControlLock) this.cleanControlLock(END_CONTROL);
            if (this.dirtyControlLock) this.cleanControlLock(CONTROL);
            if (this.dirtyEndLock) this.cleanControlLock(END);
    
            if (this.dirtyScale || this.dirtySpecies || this.dirtyDimensions || this.dirtyStart || this.dirtyStartControl || this.dirtyEndControl || this.dirtyControl || this.dirtyEnd || this.dirtyHandle) {
    
                this.dirtyPathObject = true;
  • §

    dirtySpecies flag is specific to Shape entitys

                if (this.useStartAsControlPoint && this.dirtyStart) {
    
                    this.dirtySpecies = true;
                    this.pathCalculatedOnce = false;
                }
  • §

    pathCalculatedOnce - calculating path data is an expensive operation - use this flag to limit the calculation to run only when needed

                if (this.dirtyScale || this.dirtySpecies || this.dirtyStartControl || this.dirtyEndControl || this.dirtyControl || this.dirtyEnd)  this.pathCalculatedOnce = false;
            }
    
            if (this.isBeingDragged || this.lockTo.includes(MOUSE) || this.lockTo.includes(PARTICLE)) {
    
                this.dirtyStampPositions = true;
  • §

    useStartAsControlPoint

    • When false, this flag indicates that line, quadratic and bezier shapes should treat start Coordinate updates as an instruction to move the entire Shape.
    • When true, the Coordinate is used to define the shape of the Shape relative to its other control/end Coordinates
                if (this.useStartAsControlPoint) {
    
                    this.dirtySpecies = true;
                    this.dirtyPathObject = true;
                    this.pathCalculatedOnce = false;
                }
            }
    
            if (this.dirtyScale) this.cleanScale();
    
            if (this.dirtyStart) this.cleanStart();
    
            if (this.dirtyStartControl || this.startControlLockTo === PARTICLE) this.cleanControl(START_CONTROL);
            if (this.dirtyEndControl || this.endControlLockTo === PARTICLE) this.cleanControl(END_CONTROL);
            if (this.dirtyControl || this.controlLockTo === PARTICLE) this.cleanControl(CONTROL);
            if (this.dirtyEnd || this.endLockTo === PARTICLE) this.cleanControl(END);
    
            if (this.dirtyOffset) this.cleanOffset();
            if (this.dirtyRotation) this.cleanRotation();
    
            if (this.dirtyStampPositions) this.cleanStampPositions();
  • §

    cleanSpecies - creates the SVG path String which will be used by cleanPathObject - each species creates the local path in its own way

            if (this.dirtySpecies) this.cleanSpecies();
            if (this.dirtyPathObject) this.cleanPathObject();
    
            if (this.dirtyPositionSubscribers) {
    
                this.updatePositionSubscribers();
                this.updateControlPathSubscribers();
            }
  • §

    prepareStampTabsHelper is defined in the mixin/hidden-dom-elements.js file - handles updates to anchor and button objects

            this.prepareStampTabsHelper();
        };
  • §

    cleanControlLock - internal helper function - called by prepareStamp

        P.cleanControlLock = function (label) {
    
            const capLabel = capitalize(label);
    
            this[`dirty${capLabel}Lock`] = false;
            this[`dirty${capLabel}`] = true;
        };
  • §

    cleanControl - internal helper function - called by prepareStamp

        P.cleanControl = function (label) {
    
            const capLabel = capitalize(label);
    
            this[`dirty${capLabel}`] = false;
    
            let pivot = this[`${label}Pivot`],
                path = this[`${label}Path`],
                part = this[`${label}Particle`],
                art, pathData;
    
            if (pivot && pivot.substring) {
    
                art = artefact[pivot];
    
                if (art) pivot = art;
            }
    
            if (path && path.substring) {
    
                art = artefact[path];
    
                if (art) path = art;
            }
    
            if (part && part.substring) {
    
                art = particle[part];
    
                if (art) part = art;
            }
    
            let lock = this[`${label}LockTo`],
                x, y, ox, oy, here, host, dims;
    
            const raw = this[label],
                current = this[`current${capLabel}`];
    
            if (lock === PIVOT && (!pivot || pivot.substring)) lock = COORD;
            else if (lock === PATH && (!path || path.substring)) lock = COORD;
            else if (lock === PARTICLE && (!part || part.substring)) lock = COORD;
    
            switch(lock) {
    
                case PIVOT :
    
                    if (this.pivotCorner && pivot.getCornerCoordinate) {
    
                        [x, y] = pivot.getCornerCoordinate(this[`${label}PivotCorner`]);
                    }
                    else if (pivot.type === T_ENHANCED_LABEL) {
    
                        if (this[`${label}PivotIndex`] < 0) {
    
                            if (pivot.layoutTemplate) [x, y] = pivot.layoutTemplate.currentStampPosition;
                            else this[`dirty${capLabel}`] = true;
                        }
                        else {
    
                            const check = pivot.getUnitStartAt(this[`${label}PivotIndex`]);
    
                            if (check) [x, y] = check;
                            else this[`dirty${capLabel}`] = true;
                        }
                    }
                    else [x, y] = pivot.currentStampPosition;
    
                    if (!this.addPivotOffset) {
    
                        [ox, oy] = pivot.currentOffset;
                        x -= ox;
                        y -= oy;
                    }
    
                    break;
    
                case PATH :
    
                    pathData = this.getControlPathData(path, label, capLabel);
    
                    x = pathData.x;
                    y = pathData.y;
    
                    if (!this.addPathOffset) {
    
                        x -= path.currentOffset[0];
                        y -= path.currentOffset[1];
                    }
    
                    break;
    
                case PARTICLE :
    
                    x = part.position.x;
                    y = part.position.y;
                    this.pathCalculatedOnce = false;
    
                    break;
    
                case MOUSE :
    
                    here = this.getHere();
    
                    x = here.x || 0;
                    y = here.y || 0;
    
                    break;
    
                default :
    
                    x = y = 0;
    
                    host = this.getHost();
    
                    if (host) {
    
                        dims = host.currentDimensions;
    
                        if (dims) {
    
                            this.cleanPosition(current, raw, dims);
                            [x, y] = current;
                        }
                    }
            }
    
            current[0] = x;
            current[1] = y;
    
            this.dirtySpecies = true;
            this.dirtyPathObject = true;
            this.dirtyPositionSubscribers = true;
        };
  • §

    getControlPathData - internal helper function - called by cleanControl

        P.getControlPathData = function (path, label, capLabel) {
    
            const checkAttribute = this[`current${capLabel}PathData`];
    
            if (checkAttribute) return checkAttribute;
    
            let pathPos = this[`${label}PathPosition`];
    
            const tempPos = pathPos,
                pathData = path.getPathPositionData(pathPos);
    
            if (pathPos < 0) pathPos += 1;
            if (pathPos > 1) pathPos = pathPos % 1;
    
            pathPos = parseFloat(pathPos.toFixed(6));
            if (pathPos !== tempPos) this[`${label}PathPosition`] = pathPos;
    
            if (pathData) {
    
                this[`current${capLabel}PathData`] = pathData;
                return pathData;
            }
    
            else {
    
                const host = this.getHost();
    
                if (host) {
    
                    const dims = host.currentDimensions;
    
                    if (dims) {
    
                        const current = this[`current${capLabel}`];
    
                        this.cleanPosition(current, this[label], dims);
    
                        return {
                            x: current[0],
                            y: current[1]
                        };
                    }
                }
                return {
                    x: 0,
                    y: 0
                };
            }
        };
  • §

    updateControlPathSubscribers

        P.updateControlPathSubscribers = function () {
  • §

    THIS WON’T WORK - got rid of these ‘subscriber’ Arrays!

            const items = [].concat(this.endSubscriber, this.endControlSubscriber, this.controlSubscriber, this.startControlSubscriber);
    
            let sub;
    
            items.forEach(name => {
    
                sub = artefact[name];
    
                if (sub) {
    
                    if (sub.type === T_LINE || sub.type === T_QUADRATIC || sub.type === T_BEZIER) {
    
                        if (sub.type === T_QUADRATIC) {
    
                            sub.dirtyControl = true;
                            sub.currentControlPathData = false;
                        }
    
                        else if (sub.type === T_BEZIER) {
    
                            sub.dirtyStartControl = true;
                            sub.dirtyEndControl = true;
                            sub.currentStartControlPathData = false;
                            sub.currentEndControlPathData = false;
                        }
                        sub.currentEndPathData = false;
                        sub.dirtyEnd = true;
                    }
                    sub.currentPathData = false;
                    sub.dirtyStart = true;
                }
            });
        };
    }