• Jump To … +
    ./source/core/animationloop.js ./source/core/document.js ./source/core/events.js ./source/core/init.js ./source/core/library.js ./source/core/random-seed.js ./source/core/snippets.js ./source/core/userInteraction.js ./source/core/utilities.js ./source/factory/action.js ./source/factory/anchor.js ./source/factory/animation.js ./source/factory/bezier.js ./source/factory/block.js ./source/factory/canvas.js ./source/factory/cell.js ./source/factory/cog.js ./source/factory/color.js ./source/factory/conicGradient.js ./source/factory/coordinate.js ./source/factory/crescent.js ./source/factory/element.js ./source/factory/emitter.js ./source/factory/filter.js ./source/factory/filterEngine-bluenoiseData.js ./source/factory/filterEngine.js ./source/factory/fontAttributes.js ./source/factory/gradient.js ./source/factory/grid.js ./source/factory/group.js ./source/factory/imageAsset.js ./source/factory/line.js ./source/factory/lineSpiral.js ./source/factory/loom.js ./source/factory/mesh.js ./source/factory/net.js ./source/factory/noiseAsset.js ./source/factory/oval.js ./source/factory/palette.js ./source/factory/particle.js ./source/factory/particleForce.js ./source/factory/particleHistory.js ./source/factory/particleSpring.js ./source/factory/particleWorld.js ./source/factory/pattern.js ./source/factory/phrase.js ./source/factory/picture.js ./source/factory/polygon.js ./source/factory/polyline.js ./source/factory/quadratic.js ./source/factory/quaternion.js ./source/factory/radialGradient.js ./source/factory/rawAsset.js ./source/factory/rdAsset.js ./source/factory/rectangle.js ./source/factory/renderAnimation.js ./source/factory/shape.js ./source/factory/spiral.js ./source/factory/spriteAsset.js ./source/factory/stack.js ./source/factory/star.js ./source/factory/state.js ./source/factory/tetragon.js ./source/factory/ticker.js ./source/factory/tracer.js ./source/factory/tween.js ./source/factory/unstackedElement.js ./source/factory/vector.js ./source/factory/videoAsset.js ./source/factory/wheel.js ./source/mixin/anchor.js ./source/mixin/asset.js ./source/mixin/assetAdvancedFunctionality.js ./source/mixin/assetConsumer.js ./source/mixin/base.js ./source/mixin/cascade.js ./source/mixin/delta.js ./source/mixin/displayShape.js ./source/mixin/dom.js ./source/mixin/entity.js ./source/mixin/filter.js ./source/mixin/mimic.js ./source/mixin/path.js ./source/mixin/pattern.js ./source/mixin/pivot.js ./source/mixin/position.js ./source/mixin/shapeBasic.js ./source/mixin/shapeCurve.js ./source/mixin/shapePathCalculation.js ./source/mixin/styles.js ./source/mixin/tween.js
  • §

    Crescent factory

    Crescent entitys are formed by the intersection of two circles, rendered onto a DOM <canvas> element using the Canvas 2D API’s Path2D interface - specifically the arc() method.

    • Positioning functionality for the Crescent is supplied by the position mixin, while rendering functionality comes from the entity mixin.
    • Crescent dimensions are tied closely to its radius attribute; relative dimensions are calculated using the Crescent’s Cell container’s width.
    • Crescents can use CSS color Strings for their fillStyle and strokeStyle values, alongside Gradient, RadialGradient, Color and Pattern objects.
    • They will also accept Filter objects.
    • They can use Anchor objects for user navigation.
    • They can be rendered to the canvas by including them in a Cell object’s Group.
    • They can be animated directly, or using delta animation, or act as the target for Tween animations.
    • Crescents can be cloned, and killed.
  • §

    Demos:

    • Canvas-055 - TODO
  • §

    Imports

    import { constructors, radian } from '../core/library.js';
    import { mergeOver, xt, xto, xta, isa_number, Ωempty } from '../core/utilities.js';
    import { requestCoordinate, releaseCoordinate } from './coordinate.js';
    import { requestCell, releaseCell } from './cell.js';
    
    import baseMix from '../mixin/base.js';
    import entityMix from '../mixin/entity.js';
  • §

    ensureFloat - return the value provided as a floating point number of given precision; return 0 if not a number

    const ensureFloat = (val, precision) => {
    
        val = parseFloat(val);
    
        if (!isa_number(val)) val = 0;
        if (!isa_number(precision)) precision = 0;
    
        return parseFloat(val.toFixed(precision));
    };
  • §

    Crescent constructor

    const Crescent = function (items = Ωempty) {
    
        if (!xto(items.dimensions, items.width, items.height, items.radius)) items.radius = 5;
    
        this.entityInit(items);
    
        return this;
    };
  • §

    Crescent prototype

    let P = Crescent.prototype = Object.create(Object.prototype);
    P.type = 'Crescent';
    P.lib = 'entity';
    P.isArtefact = true;
    P.isAsset = false;
  • §

    Mixins

    • base
    • entity
    P = baseMix(P);
    P = entityMix(P);
  • §

    Crescent attributes

    • Attributes defined in the base mixin: name.
    • Attributes defined in the position mixin: group, visibility, order, calculateOrder, stampOrder, start, startX, startY, handle, handleX, handleY, offset, offsetX, offsetY, dimensions, width, height, pivoted, mimicked, lockTo, lockXTo, lockYTo, scale, roll, noUserInteraction, noPositionDependencies, noCanvasEngineUpdates, noFilters, noPathUpdates, purge, bringToFrontOnDrag.
    • Attributes defined in the delta mixin: delta, noDeltaUpdates.
    • Attributes defined in the pivot mixin: pivot, pivotCorner, addPivotHandle, addPivotOffset, addPivotRotation.
    • Attributes defined in the mimic mixin: mimic, useMimicDimensions, useMimicScale, useMimicStart, useMimicHandle, useMimicOffset, useMimicRotation, useMimicFlip, addOwnDimensionsToMimic, addOwnScaleToMimic, addOwnStartToMimic, addOwnHandleToMimic, addOwnOffsetToMimic, addOwnRotationToMimic.
    • Attributes defined in the path mixin: path, pathPosition, addPathHandle, addPathOffset, addPathRotation, constantPathSpeed.
    • Attributes defined in the entity mixin: method, pathObject, winding, flipReverse, flipUpend, scaleOutline, lockFillStyleToEntity, lockStrokeStyleToEntity, onEnter, onLeave, onDown, onUp, fillStyle, strokeStyle, globalAlpha, globalCompositeOperation, lineWidth, lineCap, lineJoin, lineDash, lineDashOffset, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, filter.
    • Attributes defined in the anchor mixin: anchor.
    • Attributes defined in the filter mixin: filters, isStencil.
    let defaultAttributes = {
  • §

    outerRadius, innerRadius - the circles’ radius measured in Number pixels, or as a String% - '10%' - of the Cell’s width

        outerRadius: 20,
        innerRadius: 10,
    
        displacement: 0,
    
        displayIntersect: false,
    };
    P.defs = mergeOver(P.defs, defaultAttributes);
  • §

    Packet management

    No additional packet functionality required

  • §

    Clone management

    No additional clone functionality required

  • §

    Kill management

    No additional kill functionality required

  • §

    Get, Set, deltaSet

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

    width and height (and dimensions) values are largely irrelevant to Crescent entitys; they get used internally purely as part of the Display cycle stamp functionality.

  • §

    If they are used to (delta)set the entity’s values then outerRadius will be set to the supplied width value with String% values calculated from the entity’s host’s width, while innerRadius will be set to the supplied height value with String% values calculated from the entity’s host’s height

    S.outerRadius = function (val) {
    
        if (val != null) {
    
            this.outerRadius = val;
            this.dirtyDimensions = true;
            this.dirtyFilterIdentifier = true;
        }
    };
    D.outerRadius = function (val) {
    
        if (val != null) {
    
            this.outerRadius = addStrings(this.outerRadius, val);
            this.dirtyDimensions = true;
            this.dirtyFilterIdentifier = true;
        }
    };
    
    S.innerRadius = function (val) {
    
        if (val != null) {
    
            this.innerRadius = val;
            this.dirtyDimensions = true;
            this.dirtyFilterIdentifier = true;
        }
    };
    D.innerRadius = function (val) {
    
        if (val != null) {
    
            this.innerRadius = addStrings(this.innerRadius, val);
            this.dirtyDimensions = true;
            this.dirtyFilterIdentifier = true;
        }
    };
    
    S.width = S.outerRadius;
    D.width = D.outerRadius;
    S.height = S.innerRadius;
    D.height = D.innerRadius;
    
    S.displacement = function (val) {
    
        if (val != null && val.toFixed && val >= 0) {
    
            let d = val;
    
            if (d < 0) d = 0;
            this.displacement = d;
    
            this.dirtyDimensions = true;
            this.dirtyFilterIdentifier = true;
        }
    };
    D.displacement = function (val) {
    
        if (val != null && val.toFixed) {
    
            let d = addStrings(this.displacement, val);
    
            if (d.toFixed && d < 0) d = 0;
    
            this.displacement = d;
            this.dirtyDimensions = true;
            this.dirtyFilterIdentifier = true;
        }
    };
    
    S.displayIntersect = function (val) {
    
        this.displayIntersect = val;
        this.dirtyPathObject = true;
        this.dirtyFilterIdentifier = true;
    };
  • §

    Prototype functions

  • §

    Dimensions calculations - overwrites mixin/position.js function

    P.cleanDimensionsAdditionalActions = function () {
    
        const { outerRadius:oR, innerRadius:iR, displacement:disp } = this;
    
        let host = this.getHost(),
            hostDims;
    
        if (host) hostDims = (host.currentDimensions) ? host.currentDimensions : [host.w, host.h];
        else hostDims = [300, 150];
    
        let [w, h] = hostDims;
    
        this.currentOuterRadius = (oR.substring) ? (parseFloat(oR) / 100) * w : oR;
        this.currentInnerRadius = (iR.substring) ? (parseFloat(iR) / 100) * h : iR;
    
        this.currentDisplacement = (disp.substring) ? (parseFloat(disp) / 100) * w : disp;
    
        this.currentDimensions[0] = this.currentDimensions[1] = this.currentOuterRadius * 2;
    
        this.dirtyPathObject = true;
    
    };
  • §

    calculateInterception - internal function to calculate how the two circles interact

    P.calculateInterception = function () {
    
        if (!xta(this.currentOuterRadius, this.currentInnerRadius, this.currentDisplacement)) this.cleanDimensionsAdditionalActions();
    
        const { currentOuterRadius:oR, currentInnerRadius:iR, currentDisplacement:disp } = this;
    
        this.outerCircleStart = 0;
        this.outerCircleEnd = 360 * radian;
        this.innerCircleStart = 0;
        this.innerCircleEnd = 360 * radian;
    
        this.drawOuterCircle = false;
        this.drawDonut = false;
    
        let dMax = oR + iR,
            dMin = oR - iR;
    
        let equalCircles = (!dMin) ? true : false;
    
        if (equalCircles && !disp) this.drawOuterCircle = true;
        else {
    
            if (disp >= dMax) this.drawOuterCircle = true;
            else if (disp < dMax && disp > dMin) {
    
                let cell = requestCell();
    
                let {engine, element} = cell;
    
                let v = requestCoordinate(),
                    a, b;
  • §

    Decided to calculate the start/end angles for each circle through brute force

    • Trigonometry is the proper answer, but I can’t get the equations to stay still and play nicely
    • So instead we draw circles on a canvas and rotate a vector to see when it enters/leaves the circles
                element.width = element.width;
                engine.fillStyle = 'black';
    
                engine.save();
    
                engine.beginPath();
                engine.arc(0, 0, oR, 0, Math.PI * 2);
    
                v.setFromArray([iR, 0]);
    
                for (a = 0; a < 360; a += 0.5) {
    
                    v.rotate(0.5);
                    if(engine.isPointInPath(v[0] + disp, v[1])) break;
                }
    
                engine.restore();
                engine.save();
    
                engine.beginPath();
                engine.arc(disp, 0, iR, 0, Math.PI * 2);
    
                v.setFromArray([oR, 0]);
    
                for (b = 0; b < 360; b += 0.5) {
                    v.rotate(0.5);
                    if(!engine.isPointInPath(...v)) break;
                }
    
                engine.restore();
    
                this.outerCircleStart = -b * radian;
                this.outerCircleEnd = b * radian;
                this.innerCircleStart = a * radian;
                this.innerCircleEnd = -a * radian;
    
                releaseCoordinate(v);
                releaseCell(cell);
            }
            else this.drawDonut = true;
        }
    };
  • §

    Calculate the Crescent entity’s Path2D object

    P.cleanPathObject = function () {
    
        this.dirtyPathObject = false;
    
        if (!this.noPathUpdates || !this.pathObject) {
    
            this.calculateInterception();
    
            let { currentStampHandlePosition:handle, currentScale:scale, currentOuterRadius:oR, currentInnerRadius:iR, outerCircleStart:ocs, outerCircleEnd:oce, innerCircleStart:ics, innerCircleEnd:ice, drawOuterCircle, currentDisplacement:disp, displayIntersect } = this;
    
            let p = this.pathObject = new Path2D();
    
            oR *= scale;
            iR *= scale;
            disp *= scale;
    
            let x = oR - (handle[0] * scale),
                y = oR - (handle[1] * scale);
    
    
            if (drawOuterCircle) {
    
                p.arc(x, y, oR, ocs, oce);
                p.closePath();
    
                this.pathObjectOuter = false;
                this.pathObjectInner = false;
            }
            else {
    
                let pOuter = this.pathObjectOuter = new Path2D();
                let pInner = this.pathObjectInner = new Path2D();
            
                if (displayIntersect) p.arc(x, y, oR, ocs, oce);
                else p.arc(x, y, oR, ocs, oce, true);
    
                p.arc(x + disp, y, iR, ics, ice);
                p.closePath();
    
                pOuter.arc(x, y, oR, ocs, oce, true);
                pOuter.closePath();
    
                pInner.arc(x + disp, y, iR, ics, ice);
                pInner.closePath();
            }
        }
    };
  • §

    draw - stroke the entity outline with the entity’s strokeStyle color, gradient or pattern - including shadow

    P.draw = function (engine) {
    
        if (!this.drawDonut) engine.stroke(this.pathObject);
        else {
            engine.stroke(this.pathObjectOuter);
            engine.stroke(this.pathObjectInner);
        }
    };
  • §

    fill - fill the entity with the entity’s fillStyle color, gradient or pattern - including shadow

    P.fill = function (engine) {
    
        engine.fill(this.pathObject, this.winding);
    };
  • §

    drawAndFill - stamp the entity stroke, then fill, then remove shadow and repeat

    P.drawAndFill = function (engine) {
    
        if (!this.drawDonut) {
    
            let p = this.pathObject;
    
            engine.stroke(p);
            engine.fill(p, this.winding);
            this.currentHost.clearShadow();
            engine.stroke(p);
            engine.fill(p, this.winding);
        }
        else {
    
            let p = this.pathObject,
                pOuter = this.pathObjectOuter,
                pInner = this.pathObjectInner;
    
            engine.stroke(pOuter);
            engine.stroke(pInner);
            engine.fill(p, this.winding);
            this.currentHost.clearShadow();
            engine.stroke(pOuter);
            engine.stroke(pInner);
            engine.fill(p, this.winding);
        }
    };
  • §

    drawAndFill - stamp the entity fill, then stroke, then remove shadow and repeat

    P.fillAndDraw = function (engine) {
    
        if (!this.drawDonut) {
    
            let p = this.pathObject;
    
            engine.fill(p, this.winding);
            engine.stroke(p);
            this.currentHost.clearShadow();
            engine.fill(p, this.winding);
            engine.stroke(p);
        }
        else {
    
            let p = this.pathObject,
                pOuter = this.pathObjectOuter,
                pInner = this.pathObjectInner;
    
            engine.fill(p, this.winding);
            engine.stroke(pOuter);
            engine.stroke(pInner);
            this.currentHost.clearShadow();
            engine.fill(p, this.winding);
            engine.stroke(pOuter);
            engine.stroke(pInner);
        }
    };
  • §

    drawThenFill - stroke the entity’s outline, then fill it (shadow applied twice)

    P.drawThenFill = function (engine) {
    
        if (!this.drawDonut) {
    
            let p = this.pathObject;
    
            engine.stroke(p);
            engine.fill(p, this.winding);
        }
        else {
    
            let p = this.pathObject,
                pOuter = this.pathObjectOuter,
                pInner = this.pathObjectInner;
    
            engine.stroke(pOuter);
            engine.stroke(pInner);
            engine.fill(p, this.winding);
        }
    };
  • §

    fillThenDraw - fill the entity’s outline, then stroke it (shadow applied twice)

    P.fillThenDraw = function (engine) {
    
        if (!this.drawDonut) {
    
            let p = this.pathObject;
    
            engine.fill(p, this.winding);
            engine.stroke(p);
        }
        else {
    
            let p = this.pathObject,
                pOuter = this.pathObjectOuter,
                pInner = this.pathObjectInner;
    
            engine.fill(p, this.winding);
            engine.stroke(pOuter);
            engine.stroke(pInner);
        }
    };
  • §

    clip - restrict drawing activities to the entity’s enclosed area

    P.clip = function (engine) {
    
        engine.clip(this.pathObject, this.winding);
     };
  • §

    clear - remove everything that would have been covered if the entity had performed fill (including shadow)

    P.clear = function (engine) {
    
        let gco = engine.globalCompositeOperation;
    
        engine.globalCompositeOperation = 'destination-out';
        engine.fill(this.pathObject, this.winding);
        
        engine.globalCompositeOperation = gco;
    };
  • §

    Factory

    
    
    const makeCrescent = function (items) {
    
        if (!items) return false;
        return new Crescent(items);
    };
    
    constructors.Crescent = Crescent;
  • §

    Exports

    export {
        makeCrescent,
    };