• Jump To … +
    ./source/core/animationloop.js ./source/core/component.js ./source/core/document.js ./source/core/events.js ./source/core/init.js ./source/core/library.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/coordinate.js ./source/factory/element.js ./source/factory/emitter.js ./source/factory/filter.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/loom.js ./source/factory/mesh.js ./source/factory/net.js ./source/factory/noise.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/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/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 ./source/worker/filter-string.js ./source/worker/filter.js
  • ¶

    Net factory

    The Scrawl-canvas particle physics engine is a simple system designed to allow developers a way to add particle-based effects to their canvas animation scenes. The physics engine is built on top of the following components:

    • Particle objects, which represent a 3-dimensional coordinate - based on a Scrawl-canvas Vector object - and include a history of recent positions which we can use to determine how to display that particle on screen.
    • History arrays which can be pooled (reused) to cut down on Array creation and distruction during the animation.
    • Force objects which define the general and occasional forces to be applied to each particle in the system as the animation progresses - a gravity force object is pre-defined by Scrawl-canvas.
    • Spring objects used to define a constraint (connection) between two particles in a system.
    • World objects where we can store attributes and values used by various objects; these attributes can be set up so that they will be inherited by clones of the World object. We can also influence the speed of the physics animation here.

    We do not have to handle particle generation and manipulation ourselves. Instead, Scrawl-canvas gives us three dedicated entitys which we use to add particle animation effects to the canvas scene. These entitys are:

    • Tracer - this entity generates a single non-recycled (in other words: long lasting) particle with a history, which we can use to display trace effects in the animation.
    • Emitter - an entity which generates a stream of short-lived, recycled particles, each with its own history. Emitters are highly versatile entitys which can generate a wide range of effects.
    • Net - a (generally) larger entity which uses both forces and springs to manage the animation of its non-recycled particles. Note that other artefacts can use Net particles as a reference for their own positioning.
  • ¶

    Demos:

    • particles-008 - Net entity: generation and basic functionality, including Spring objects
    • particles-009 - Net particles: drag-and-drop functionality
    • particles-010 - Net entity: using a shape path as a net template
    • particles-012 - Use Net entity particles as reference coordinates for other artefacts
  • ¶

    Imports

    import { constructors, artefact, artefactnames, entity, world, particle } from '../core/library.js';
    import { pushUnique, mergeOver, λnull, isa_fn, isa_obj, xt, xta } from '../core/utilities.js';
    import { currentGroup } from '../core/document.js';
    
    import { makeParticle } from './particle.js';
    import { requestVector, releaseVector } from './vector.js';
    import { makeSpring } from './particleSpring.js';
    
    import baseMix from '../mixin/base.js';
    import entityMix from '../mixin/entity.js';
  • ¶

    Net constructor

    const Net = function (items = {}) {
    
        this.makeName(items.name);
        this.register();
        this.initializePositions();
        this.set(this.defs);
  • ¶

    The entity places a hitRadius around each of its particles - effectively a distributed hit zone which can be used for drag-and-drop, and other user interactions..

        this.onEnter = λnull;
        this.onLeave = λnull;
        this.onDown = λnull;
        this.onUp = λnull;
  • ¶

    Net entitys generate their Particles once, on initialization, in line with the generate function’s instructions. Scrtawl-canvas includes a number of pre-built generator functions, which we can reference by their String names: weak-net, strong-net, weak-shape, strong-shape

        this.generate = λnull;
  • ¶

    The postGenerate function runs imediately after the generate function. We can use it to amend the attributes of selected Particles, for instance to make them static.

        this.postGenerate = λnull;
  • ¶

    As part of its stamp functionality the Net entity will invoke the stampAction function. If not supplied, the entity will not display anything on the canvas.

        this.stampAction = λnull;
  • ¶

    Setup the particle store, and the entity’s springs Array

        this.particleStore = [];
        this.springs = [];
    
        if (!items.group) items.group = currentGroup;
    
        this.set(items);
    
        if (this.purge) this.purgeArtefact(this.purge);
    
        return this;
    };
  • ¶

    Net prototype

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

    Mixins

    P = baseMix(P);
    P = entityMix(P);
  • ¶

    Net attributes

    • Attributes defined in the base mixin: name.
    • Attributes defined in the position mixin: group, visibility, order, 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 = {
  • ¶

    world - World object; can be set using the String name of a World object, or the World object itself.

        world: null,
  • ¶

    artefact - In theory, any Scrawl-canvas object whose isArtefact flag is set to true can be assigned to this attribute. However this has not been tested on non-entity artefacts. For now, stick to Scrawl-canvas entity objects.

    • Can be set using the String name of an artefact object, or the artefact object itself.
        artefact: null,
  • ¶

    historyLength - positive integer Number - every Particle will keep a record of its recent state, in a set of ParticleHistory arrays stored in the Particle’s history Array. The Net entity will set the maximum permitted length of the history array whenever it generates a new Particle.

        historyLength: 1,
  • ¶

    Net entitys will, as part of the Display cycle, apply any force objects assigned to a Particle. The initial forces assigned to every new Particle will be in line with the Force objects included in the Net’s forces Array.

    • To set the Array, supply a new Array containing Force objects, and/or the name Strings of those Force objects, to the forces attribute.
        forces: null,
  • ¶

    mass - positive float Number - the initial mass assigned to each Particle when it is generated. Note that, unlike Net entitys, a Net entity will generate all its Particles with the same mass value

    • The mass attribute is used by the pre-defined gravity Force
        mass: 1,
  • ¶

    Physics calculations are handled by the Net entity’s physics engine which must be a String value of either euler (the default engine), improved-euler or runge-kutta.

        engine: 'euler',
  • ¶

    springConstant, damperConstant - positive float Numbers - used for the initial values for any Spring objects generated to connect two Particles

        springConstant: 50,
        damperConstant: 10,
  • ¶

    This is a ratio figure, not the actual rest length. If == 1, spring restLength will equal the initial distance between the two particles. < 1 and the length will be proportionately smaller; > 1 gives results in a longer length

        restLength: 1,
  • ¶

    showSprings - Boolean flag - when set, Scrawl-canvas will display the Spring connections between Particle pairs

        showSprings: false,
  • ¶

    showSpringColor - CSS color String - the strokeStyle color used to display the Spring objects

        showSpringsColor: '#000000',
  • ¶
    weak-net, strong-net

    rows, columns - positive integer Numbers

        rows: 0,
        columns: 0,
  • ¶

    rowDistance, columnDistance - positive float Numbers, representing the absolute pixel distance between rows and columns, or percentage String values representing the distance as measured relative to the host Cell’s dimensions

        rowDistance: 0,
        columnDistance: 0,
  • ¶
    weak-shape, strong-shape

    shapeTemplate - String name of a shape-based entity, or the entity object itself - used as the template along which particles will be generated

        shapeTemplate: null,
  • ¶

    precision - positive integer Number - the number of particles to generate along the template path

        precision: 20,
  • ¶

    joinTemplateEnds - Boolean flag - when set, springs will be generated to connect the start and the end particles on the template path

        joinTemplateEnds: false,
    
        particlesAreDraggable: false,
  • ¶

    Note that the hitRadius attribute - a positive float Number - is absolute, and has nothing to do with the entity’s dimensions. Net entity hit zones are tied to its Particles, not its start coordinate.

        hitRadius: 10,
  • ¶

    showHitRadius - Boolean flag - when set, Scrawl-canvas will display appropriate circles around each of the Net entity’s Particles (in addition to any artefact stamped at the Particle’s position)

        showHitRadius: false,
  • ¶

    hitRadiusColor - CSS color String - the strokeStyle color used to display the hit radius

        hitRadiusColor: '#000000',
  • ¶

    resetAfterBlur - positive float Number (measuring seconds) - physics simulations can be brittle, particularly if they are forced to calculate Particle loads (accelerations), velocities and speeds over a large time step. Rather than manage that time step in cases where the user may neglect or navigate away from the browser tab containing the physics animation, Scrawl-canvas will stop, clear, and recreate the scene if the time it takes the user to return to (re-focus on) the web page is greater than the value set in this attribute.

        resetAfterBlur: 3,
  • ¶
    Not defined in the defs object, but set up in the constructor and setters
  • ¶

    particleStore - an Array where all the Net’s current particles will be stored. To render the entity, we need to iterate through these particles and use them to repeatedly stamp the Net’s artefact - or perform equivalent <canvas> context engine instructions - onto the host Cell. These actions will be defined in the stampAction function.

  • ¶

    stampAction - define all major rendering actions in this function. The function receives the following arguments: (artefact, particle, host) - where artefact is the Net entity’s artefact object (if any has been defined/set); particle is the current Particle object whose history needs to be rendered onto the canvas; and host is the Cell wrapper on which we will draw our graphics

  • ¶

    springs - Array - holds all the entity’s Spring objects

    };
    P.defs = mergeOver(P.defs, defaultAttributes);
  • ¶

    Packet management

    P.packetExclusions = pushUnique(P.packetExclusions, ['forces', 'springs', 'particleStore']);
    P.packetExclusionsByRegex = pushUnique(P.packetExclusionsByRegex, []);
    P.packetCoordinates = pushUnique(P.packetCoordinates, []);
    P.packetObjects = pushUnique(P.packetObjects, ['world', 'artefact', 'shapeTemplate']);
    P.packetFunctions = pushUnique(P.packetFunctions, ['generate', 'postGenerate', 'stampAction']);
    
    P.finalizePacketOut = function (copy, items) {
    
        let forces = items.forces || this.forces || false;
        if (forces) {
    
            let tempForces = [];
            forces.forEach(f => {
    
                if (f.substring) tempForces.push(f);
                else if (isa_obj(f) && f.name) tempForces.push(f.name);
            });
            copy.forces = tempForces;
        }
    
        let tempParticles = [];
        this.particleStore.forEach(p => tempParticles.push(p.saveAsPacket()));
        copy.particleStore = tempParticles;
    
        return copy;
    };
  • ¶

    Clone management

    P.postCloneAction = function(clone, items) {
    
        return clone;
    };
  • ¶

    Kill management

    P.factoryKill = function (killArtefact, killWorld) {
    
        this.isRunning = false;
        if (killArtefact) {
    
            this.artefact.kill();
            if (this.shapeTemplate) this.shapeTemplate.kill();
        }
    
        if (killWorld) this.world.kill();
        this.purgeParticlesFromLibrary();
    };
    
    P.purgeParticlesFromLibrary = function () {
    
        let {particleStore, springs} = this;
  • ¶

    Net entity particles can be referenced by other artefacts (when using them for positioning coordinates. New particles will be created with the same names as the old ones, so it is enough to replace these references with the String names of the particles)

        artefactnames.forEach(a => {
    
            let tempArt = artefact[a];
    
            if (tempArt) {
    
                if (tempArt.particle && !tempArt.particle.substring && tempArt.particle.name) tempArt.particle = tempArt.particle.name;
  • ¶

    Polyline entitys go one step further in that they can also use Particles in their pin array

                if (tempArt.type === 'Polyline' && tempArt.useParticlesAsPins) {
    
                    tempArt.pins.forEach((pin, index) => {
    
                        if (isa_obj(pin) && pin.type === 'Particle') {
    
                            tempArt.pins[index] = pin.name;
                            tempArt.dirtyPins = true;
                        }
                    });
                }
            }
        });
  • ¶

    Now we can tell all the Net entity’s particles to kill themselves

        particleStore.forEach(p => p.kill());
        particleStore.length = 0;
  • ¶

    We can also get rid of all the Spring objects as they will be recreated alongside the particle objects as part of the Net entity’s generate functionality.

        springs.forEach(s => s.kill());
        springs.length = 0;
    };
  • ¶

    Get, Set, deltaSet

    let G = P.getters,
        S = P.setters,
        D = P.deltaSetters;
    
    S.generate = function (item) {
    
        if (isa_fn(item)) this.generate = item;
    
        else if (item.substring && generators[item]) this.generate = generators[item];
    };
    
    S.postGenerate = function (item) {
    
        if (isa_fn(item)) this.postGenerate = item;
    };
    
    S.stampAction = function (item) {
    
        if (isa_fn(item)) this.stampAction = item;
    };
    
    
    S.world = function (item) {
    
        let w;
    
        if (item.substring) w = world[item];
        else if (isa_obj(item) && item.type === 'World') w = item;
    
        if (w) this.world = w;
    };
    
    S.artefact = function (item) {
    
        let art;
    
        if (item.substring) art = artefact[item];
        else if (isa_obj(item) && item.isArtefact) art = item;
    
        if (art) this.artefact = art;
    };
    
    S.shapeTemplate = function (item) {
    
        let art;
    
        if (item.substring) art = entity[item];
        else if (isa_obj(item) && item.isArtefact && xt(item.species)) art = item;
    
        if (art) this.shapeTemplate = art;
    };
  • ¶

    Prototype functions

  • ¶

    regularStampSynchronousActions - overwriters the functionality defined in the entity.js mixin

    P.regularStampSynchronousActions = function () {
    
        let {world, artefact:art, particleStore, springs, generate, postGenerate, stampAction, lastUpdated, resetAfterBlur, showSprings, showSpringsColor, showHitRadius, hitRadius, hitRadiusColor} = this;
    
        let globalAlpha = 1,
            globalCompositeOperation = 'source-over';
    
        if (this.state) {
            globalAlpha = this.state.globalAlpha;
            globalCompositeOperation = this.state.globalCompositeOperation;
        }
    
        let host = this.currentHost;
  • ¶

    The particle system is a physics system, which means we need to advance it by a small amount of time as part of each Display cycle

        let deltaTime = 16 / 1000,
            now = Date.now();
    
        if (lastUpdated) deltaTime = (now - lastUpdated) / 1000;
  • ¶

    If the user has focussed on another tab in the browser before returning to the tab running this Scrawl-canvas animation, then we risk breaking the page by continuing the animation with the existing particles - simplest solution is to remove all the particles and, in effect, restart the Net’s animation.

        if (deltaTime > resetAfterBlur) {
    
            this.purgeParticlesFromLibrary();
            deltaTime = 16 / 1000;
        }
  • ¶

    If we have no particles, we need to generate them

        if (!particleStore.length) {
    
            generate.call(this, host);
            postGenerate.call(this);
        }
  • ¶

    The physics core of the function:

    • Calculate the forces acting on each of the Net entity’s particles
    • Add the Spring constraints to the particles
    • Get the particles to update themselves, using the appropriate physics engine
        particleStore.forEach(p => p.applyForces(world, host));
        springs.forEach(s => s.applySpring());
        particleStore.forEach(p => p.update(deltaTime, world));
  • ¶

    Additional visuals are available - specifically we can draw in the springs acting on particle pairs before we stamp the artefact on them

        if (showSprings) {
    
            let engine = host.engine;
    
            engine.save();
            engine.globalAlpha = globalAlpha;
            engine.globalCompositeOperation = globalCompositeOperation;
            engine.strokeStyle = showSpringsColor;
            engine.shadowOffsetX = 0,
            engine.shadowOffsetY = 0,
            engine.shadowBlur = 0,
            engine.shadowColor = 'rgba(0,0,0,0)',
            engine.lineWidth = 1;
            engine.setTransform(1, 0, 0, 1, 0, 0);
            engine.beginPath();
    
            springs.forEach(s => {
    
                let {particleFrom, particleTo} = s;
    
                engine.moveTo(particleFrom.position.x, particleFrom.position.y);
                engine.lineTo(particleTo.position.x, particleTo.position.y);
            });
            engine.stroke();
            engine.restore();
        }
  • ¶

    The display cycle core of the function

    • Get each particle to update its history
    • Using that history, get each particle to stamp some ink onto the canvas.
        particleStore.forEach(p => {
    
            p.manageHistory(deltaTime, host);
            stampAction.call(this, art, p, host);
        });
  • ¶

    A second set of additional visuals - display each particle’s hit region

        if (showHitRadius) {
    
            let engine = host.engine;
    
            engine.save();
            engine.globalAlpha = globalAlpha;
            engine.globalCompositeOperation = globalCompositeOperation;
            engine.lineWidth = 1;
            engine.strokeStyle = hitRadiusColor;
            engine.shadowOffsetX = 0,
            engine.shadowOffsetY = 0,
            engine.shadowBlur = 0,
            engine.shadowColor = 'rgba(0,0,0,0)',
    
            engine.setTransform(1, 0, 0, 1, 0, 0);
            engine.beginPath();
    
            particleStore.forEach(p => {
                engine.moveTo(p.position.x, p.position.y);
                engine.arc(p.position.x, p.position.y, hitRadius, 0, Math.PI * 2);
            });
            
            engine.stroke();
            engine.restore();
        }
  • ¶

    The final critical step - remember the absolute time value of when we started to perform this Display cycle

        this.lastUpdated = now;
    };
  • ¶

    restart - force the Net to purge its particles/springs, rebuild itself, and restart its animation from the beginning

    P.restart = function () {
    
        this.purgeParticlesFromLibrary();
        
        this.lastUpdated = Date.now();
    
        return this;
    };
  • ¶

    checkHit - overwrites the function defined in mixin/position.js

    • The Net entity’s hit areas are circles centred on the entity’s Particle’s positions.
    • Nets cannot be dragged; the Particles that make up the Net can be dragged.
    P.checkHit = function (items = [], mycell) {
    
        this.lastHitParticle = null;
    
        if (!this.particlesAreDraggable) return false;
    
        if (this.noUserInteraction) return false;
    
        let tests = (!Array.isArray(items)) ?  [items] : items;
    
        let particleStore = this.particleStore,
            res = false,
            tx, ty, i, iz, p;
    
        if (tests.some(test => {
    
            if (Array.isArray(test)) {
    
                tx = test[0];
                ty = test[1];
            }
            else if (xta(test, test.x, test.y)) {
    
                tx = test.x;
                ty = test.y;
            }
            else return false;
    
            if (!tx.toFixed || !ty.toFixed || isNaN(tx) || isNaN(ty)) return false;
    
            let v = requestVector();
    
            for (i = 0, iz = particleStore.length; i < iz; i++) {
    
                p = particleStore[i];
    
                v.set(p.position).vectorSubtract(test);
    
                if (v.getMagnitude() < this.hitRadius) {
    
                    res = p;
                    break;
                }
            }
            releaseVector(v);
    
            return res;
    
        }, this)) {
    
            let r = this.checkHitReturn(tx, ty, mycell, res);
    
            this.lastHitParticle = res;
    
            return r;
        }
        return false;
    };
  • ¶

    checkHitReturn - overwrites the function defined in mixin/position.js

    • The return object includes the Particle object that recorded the hit, saved in the object’s particle attribute
    P.checkHitReturn = function (x, y, cell, particle) {
    
        return {
            x: x,
            y: y,
            artefact: this,
            particle: particle,
        };
    };
  • ¶

    pickupArtefact - overwrites the function defined in mixin/position.js

    • One of the entity’s Particle objects is being dragged, not the entity itself
    P.pickupArtefact = function (items = {}) {
    
        let particle = this.lastHitParticle;
    
        if (xta(items, particle)) {
    
            particle.isBeingDragged = items;
            particle.dragOffset = requestVector(particle.position).vectorSubtract(items);
            this.lastHitParticle
        }
        return this;
    };
  • ¶

    dropArtefact - overwrites the function defined in mixin/position.js

    • One of the entity’s Particle objects is being dragged, not the entity itself
    P.dropArtefact = function () {
    
        this.lastHitParticle.isBeingDragged = null;
        releaseVector(this.lastHitParticle.dragOffset);
        this.lastHitParticle.dragOffset = null;
        this.lastHitParticle = null;
        return this;
    };
  • ¶

    Pre-defined Net generators

    const generators = {
  • ¶

    weak-net - a net made up of rows and columns, with particles at each row/column intersection. The generator will connect each Particle with springs to up to four of its closest horizontal and vertical neighbors

    • Can be used to model cloth
        'weak-net': function (host) {
    
            let { particleStore, artefact:art, historyLength, engine, forces, springs, mass, rows, columns, rowDistance, columnDistance, showSprings, showSpringsColor, name, springConstant, damperConstant, restLength } = this;
    
            if (host && rows > 0 && columns > 0) {
    
                let [x, y] = this.currentStampPosition;
                let [width, height] = host.currentDimensions;
    
                let deltaR = (rowDistance.substring) ? (parseFloat(rowDistance) / 100) * height : rowDistance;
                let deltaC = (columnDistance.substring) ? (parseFloat(columnDistance) / 100) * height : columnDistance;
    
                let dx, dy, p, i, j;
  • ¶

    generate particles

                for (i = 0; i < rows; i++) {
    
                    dy = (deltaR * i) + y;
    
                    for (j = 0; j < columns; j++) {
    
                        dx = (deltaC * j) + x;
    
                        p = makeParticle({
    
                            name: `${name}-${i}-${j}`,
    
                            positionX: dx,
                            positionY: dy,
                            positionZ: 0,
    
                            velocityX: 0,
                            velocityY: 0,
                            velocityZ: 0,
    
                            historyLength, 
                            engine, 
                            forces, 
    
                            mass,
    
                            fill: art.get('fillStyle'),
                            stroke: art.get('strokeStyle'),
                        });
    
                        p.run(0, 0, false);
    
                        particleStore.push(p);
                    }
                }
    
                const springMaker = function (particleFrom, particleTo, springName) {
    
                    let v, l, s;
    
                    v = requestVector(particleFrom.position).vectorSubtract(particleTo.position);
                    l = v.getMagnitude();
    
                    s = makeSpring({
    
                        name: springName,
    
                        particleFrom,
                        particleTo,
    
                        springConstant, 
                        damperConstant,
    
                        restLength: l * restLength,
                    });
    
                    springs.push(s);
                    releaseVector(v);
                };
    
                let f, t;
  • ¶

    generate springs

                for (i = 0; i < rows; i++) {
    
                    for (j = 0; j < columns - 1; j++) {
    
                        f = particle[`${name}-${i}-${j}`];
                        t = particle[`${name}-${i}-${j + 1}`];
                        springMaker(f, t, `${name}-${i}-${j}~${name}-${i}-${j + 1}`);
                    }
                }
    
                for (i = 0; i < columns; i++) {
    
                    for (j = 0; j < rows - 1; j++) {
    
                        f = particle[`${name}-${j}-${i}`];
                        t = particle[`${name}-${j + 1}-${i}`];
                        springMaker(f, t, `${name}-${j}-${i}~${name}-${j + 1}-${i}`);
                    }
                }
            }
        },
  • ¶

    strong-net - a net made up of rows and columns, with particles at each row/column intersection. The generator will connect each Particle with springs to up to eight of its closest horizontal, vertical and diagonal neighbors

    • Can be used to model a soft-bodied object
        'strong-net': function (host) {
    
            let { particleStore, artefact:art, historyLength, engine, forces, springs, mass, rows, columns, rowDistance, columnDistance, showSprings, showSpringsColor, name, springConstant, damperConstant, restLength } = this;
    
            if (host && rows > 0 && columns > 0) {
    
                let [x, y] = this.currentStampPosition;
                let [width, height] = host.currentDimensions;
    
                let deltaR = (rowDistance.substring) ? (parseFloat(rowDistance) / 100) * height : rowDistance;
                let deltaC = (columnDistance.substring) ? (parseFloat(columnDistance) / 100) * height : columnDistance;
    
                let dx, dy, p, i, j;
  • ¶

    generate particles

                for (i = 0; i < rows; i++) {
    
                    dy = (deltaR * i) + y;
    
                    for (j = 0; j < columns; j++) {
    
                        dx = (deltaC * j) + x;
    
                        p = makeParticle({
    
                            name: `${name}-${i}-${j}`,
    
                            positionX: dx,
                            positionY: dy,
                            positionZ: 0,
    
                            velocityX: 0,
                            velocityY: 0,
                            velocityZ: 0,
    
                            historyLength, 
                            engine, 
                            forces, 
    
                            mass,
    
                            fill: art.get('fillStyle'),
                            stroke: art.get('strokeStyle'),
                        });
    
                        p.run(0, 0, false);
    
                        particleStore.push(p);
                    }
                }
    
                const springMaker = function (particleFrom, particleTo, springName) {
    
                    let v, l, s;
    
                    v = requestVector(particleFrom.position).vectorSubtract(particleTo.position);
                    l = v.getMagnitude();
    
                    s = makeSpring({
    
                        name: springName,
    
                        particleFrom,
                        particleTo,
    
                        springConstant, 
                        damperConstant,
    
                        restLength: l * restLength,
                    });
    
                    springs.push(s);
                    releaseVector(v);
                };
    
                let f, t;
  • ¶

    generate springs

                for (i = 0; i < rows; i++) {
    
                    for (j = 0; j < columns - 1; j++) {
    
                        f = particle[`${name}-${i}-${j}`];
                        t = particle[`${name}-${i}-${j + 1}`];
                        springMaker(f, t, `${name}-${i}-${j}~${name}-${i}-${j + 1}`);
                    }
                }
    
                for (i = 0; i < columns; i++) {
    
                    for (j = 0; j < rows - 1; j++) {
    
                        f = particle[`${name}-${j}-${i}`];
                        t = particle[`${name}-${j + 1}-${i}`];
                        springMaker(f, t, `${name}-${j}-${i}~${name}-${j + 1}-${i}`);
                    }
                }
    
                for (i = 0; i < columns - 1; i++) {
    
                    for (j = 0; j < rows - 1; j++) {
    
                        f = particle[`${name}-${j}-${i}`];
                        t = particle[`${name}-${j + 1}-${i + 1}`];
                        springMaker(f, t, `${name}-${j}-${i}~${name}-${j + 1}-${i + 1}`);
                    }
                }
    
                for (i = 0; i < columns - 1; i++) {
    
                    for (j = rows - 1; j > 0; j--) {
    
                        f = particle[`${name}-${j}-${i}`];
                        t = particle[`${name}-${j - 1}-${i + 1}`];
                        springMaker(f, t, `${name}-${j}-${i}~${name}-${j - 1}-${i + 1}`);
                    }
                }
            }
        },
  • ¶

    weak-shape - Warning: not very stable! - a rope of Particles set along a path. The generator will connect each Particle with springs to up to six of its closest neighbors

        'weak-shape': function (host) {
    
            let { particleStore, artefact:art, historyLength, engine, forces, springs, mass, showSprings, showSpringsColor, name, springConstant, damperConstant, restLength, shapeTemplate, precision, joinTemplateEnds } = this;
    
            const springMaker = function (particleFrom, particleTo, springName) {
    
                let v, l, s;
    
                v = requestVector(particleFrom.position).vectorSubtract(particleTo.position);
                l = v.getMagnitude();
    
                s = makeSpring({
    
                    name: springName,
    
                    particleFrom,
                    particleTo,
    
                    springConstant, 
                    damperConstant,
    
                    restLength: l * restLength,
                });
    
                springs.push(s);
                releaseVector(v);
            };
    
            let i, p, f, t;
    
            if (shapeTemplate && precision) {
    
                for (i = 0; i < precision; i++) {
    
                    let coords = shapeTemplate.getPathPositionData(i / precision);
    
                    p = makeParticle({
    
                        name: `${name}-${i}`,
    
                        positionX: coords.x,
                        positionY: coords.y,
                        positionZ: 0,
    
                        velocityX: 0,
                        velocityY: 0,
                        velocityZ: 0,
    
                        historyLength,
                        engine,
                        forces,
    
                        mass,
    
                        fill: art.get('fillStyle'),
                        stroke: art.get('strokeStyle'),
                    });
    
                    p.run(0, 0, false);
    
                    particleStore.push(p);
                }
    
                for (i = 0; i < precision - 1; i++) {
    
                    f = particle[`${name}-${i}`];
                    t = particle[`${name}-${i + 1}`];
                    springMaker(f, t, `${name}-${i}~${name}-${i + 1}`);
                }
    
                if (joinTemplateEnds) {
    
                    f = particle[`${name}-${precision - 1}`];
                    t = particle[`${name}-${0}`];
                    springMaker(f, t, `${name}-${precision - 1}~${name}-${0}`);
                }
    
                for (i = 0; i < precision - 2; i++) {
    
                    f = particle[`${name}-${i}`];
                    t = particle[`${name}-${i + 2}`];
                    springMaker(f, t, `${name}-${i}~${name}-${i + 2}`);
                }
    
                if (joinTemplateEnds) {
    
                    f = particle[`${name}-${precision - 2}`];
                    t = particle[`${name}-${0}`];
                    springMaker(f, t, `${name}-${precision - 2}~${name}-${0}`);
    
                    f = particle[`${name}-${precision - 1}`];
                    t = particle[`${name}-${1}`];
                    springMaker(f, t, `${name}-${precision - 1}~${name}-${1}`);
                }
    
                for (i = 0; i < precision - 3; i++) {
    
                    f = particle[`${name}-${i}`];
                    t = particle[`${name}-${i + 3}`];
                    springMaker(f, t, `${name}-${i}~${name}-${i + 3}`);
                }
    
                if (joinTemplateEnds) {
    
                    f = particle[`${name}-${precision - 3}`];
                    t = particle[`${name}-${0}`];
                    springMaker(f, t, `${name}-${precision - 3}~${name}-${0}`);
    
                    f = particle[`${name}-${precision - 2}`];
                    t = particle[`${name}-${1}`];
                    springMaker(f, t, `${name}-${precision - 2}~${name}-${1}`);
    
                    f = particle[`${name}-${precision - 1}`];
                    t = particle[`${name}-${2}`];
                    springMaker(f, t, `${name}-${precision - 1}~${name}-${2}`);
                }
            }
        },
  • ¶

    strong-shape - Warning: generally unstable! - a rope of Particles set along a path. The generator will connect each Particle with springs to up to six of its closest neighbors, and make an additional connection with a Particle at some distance from it (to act as a strut)

        'strong-shape': function (host) {
    
            let { particleStore, artefact:art, historyLength, engine, forces, springs, mass, showSprings, showSpringsColor, name, springConstant, damperConstant, restLength, shapeTemplate, precision, joinTemplateEnds } = this;
    
            const springMaker = function (particleFrom, particleTo, springName) {
    
                let v, l, s;
    
                v = requestVector(particleFrom.position).vectorSubtract(particleTo.position);
                l = v.getMagnitude();
    
                s = makeSpring({
    
                    name: springName,
    
                    particleFrom,
                    particleTo,
    
                    springConstant, 
                    damperConstant,
    
                    restLength: l * restLength,
                });
    
                springs.push(s);
                releaseVector(v);
            };
    
            let i, p, f, t;
    
            if (shapeTemplate && precision) {
    
                for (i = 0; i < precision; i++) {
    
                    let coords = shapeTemplate.getPathPositionData(i / precision);
    
                    p = makeParticle({
    
                        name: `${name}-${i}`,
    
                        positionX: coords.x,
                        positionY: coords.y,
                        positionZ: 0,
    
                        velocityX: 0,
                        velocityY: 0,
                        velocityZ: 0,
    
                        historyLength,
                        engine,
                        forces,
    
                        mass,
    
                        fill: art.get('fillStyle'),
                        stroke: art.get('strokeStyle'),
                    });
    
                    p.run(0, 0, false);
    
                    particleStore.push(p);
                }
    
                for (i = 0; i < precision - 1; i++) {
    
                    f = particle[`${name}-${i}`];
                    t = particle[`${name}-${i + 1}`];
                    springMaker(f, t, `${name}-${i}~${name}-${i + 1}`);
                }
    
                if (joinTemplateEnds) {
    
                    f = particle[`${name}-${precision - 1}`];
                    t = particle[`${name}-${0}`];
                    springMaker(f, t, `${name}-${precision - 1}~${name}-${0}`);
                }
    
                for (i = 0; i < precision - 2; i++) {
    
                    f = particle[`${name}-${i}`];
                    t = particle[`${name}-${i + 2}`];
                    springMaker(f, t, `${name}-${i}~${name}-${i + 2}`);
                }
    
                if (joinTemplateEnds) {
    
                    f = particle[`${name}-${precision - 2}`];
                    t = particle[`${name}-${0}`];
                    springMaker(f, t, `${name}-${precision - 2}~${name}-${0}`);
    
                    f = particle[`${name}-${precision - 1}`];
                    t = particle[`${name}-${1}`];
                    springMaker(f, t, `${name}-${precision - 1}~${name}-${1}`);
                }
    
                for (i = 0; i < precision - 3; i++) {
    
                    f = particle[`${name}-${i}`];
                    t = particle[`${name}-${i + 3}`];
                    springMaker(f, t, `${name}-${i}~${name}-${i + 3}`);
                }
    
                if (joinTemplateEnds) {
    
                    f = particle[`${name}-${precision - 3}`];
                    t = particle[`${name}-${0}`];
                    springMaker(f, t, `${name}-${precision - 3}~${name}-${0}`);
    
                    f = particle[`${name}-${precision - 2}`];
                    t = particle[`${name}-${1}`];
                    springMaker(f, t, `${name}-${precision - 2}~${name}-${1}`);
    
                    f = particle[`${name}-${precision - 1}`];
                    t = particle[`${name}-${2}`];
                    springMaker(f, t, `${name}-${precision - 1}~${name}-${2}`);
                }
    
                let halfPrecision = Math.floor(precision / 2);
    
                for (i = 0; i < precision - halfPrecision; i++) {
    
                    f = particle[`${name}-${i}`];
    
                    if (i + halfPrecision < precision - 1) {
    
                        t = particle[`${name}-${i + halfPrecision}`];
                        springMaker(f, t, `${name}-${i}~${name}-${i + halfPrecision}`);
                    }
                }
            }
        },
  • ¶

    hub-spoke - Warning: highly unstable! - a rope of Particles set along a path. The generator will connect each Particle with springs to its closest neighbors, and make an additional connection with a ‘hub’ particle at the template’s rotation-reflection point.

        'hub-spoke': function (host) {
    
            let { shapeTemplate, precision } = this;
    
            if (shapeTemplate && shapeTemplate.type && precision) {
    
                let { particleStore, artefact:art, historyLength, engine, forces, springs, mass, showSprings, showSpringsColor, name, springConstant, damperConstant, restLength, joinTemplateEnds } = this;
    
                const springMaker = function (particleFrom, particleTo, springName) {
    
                    let v, l, s;
    
                    v = requestVector(particleFrom.position).vectorSubtract(particleTo.position);
                    l = v.getMagnitude();
    
                    s = makeSpring({
    
                        name: springName,
    
                        particleFrom,
                        particleTo,
    
                        springConstant, 
                        damperConstant,
    
                        restLength: l * restLength,
                    });
    
                    springs.push(s);
                    releaseVector(v);
                };
    
                let i, p, f, t, hub;
    
                if (['Bezier', 'Line', 'Oval', 'Polygon', 'Polyline', 'Quadratic', 'Rectangle', 'Shape', 'Spiral', 'Star', 'Tetragon'].indexOf(shapeTemplate.type) >= 0) {
  • ¶

    build the rim

                    for (i = 0; i < precision; i++) {
    
                        let coords = shapeTemplate.getPathPositionData(i / precision);
    
                        p = makeParticle({
    
                            name: `${name}-${i}`,
    
                            positionX: coords.x,
                            positionY: coords.y,
                            positionZ: 0,
    
                            velocityX: 0,
                            velocityY: 0,
                            velocityZ: 0,
    
                            historyLength,
                            engine,
                            forces,
    
                            mass,
    
                            fill: art.get('fillStyle'),
                            stroke: art.get('strokeStyle'),
                        });
    
                        p.run(0, 0, false);
    
                        particleStore.push(p);
                    }
    
                    for (i = 0; i < precision - 1; i++) {
    
                        f = particle[`${name}-${i}`];
                        t = particle[`${name}-${i + 1}`];
                        springMaker(f, t, `${name}-${i}-${i + 1}`);
                    }
    
                    if (joinTemplateEnds) {
    
                        f = particle[`${name}-${precision - 1}`];
                        t = particle[`${name}-${0}`];
                        springMaker(f, t, `${name}-${precision - 1}-0`);
                    }
    
                    for (i = 0; i < precision - 2; i++) {
    
                        f = particle[`${name}-${i}`];
                        t = particle[`${name}-${i + 2}`];
                        springMaker(f, t, `${name}-${i}~${name}-${i + 2}`);
                    }
    
                    if (joinTemplateEnds) {
    
                        f = particle[`${name}-${precision - 2}`];
                        t = particle[`${name}-${0}`];
                        springMaker(f, t, `${name}-${precision - 2}~${name}-${0}`);
    
                        f = particle[`${name}-${precision - 1}`];
                        t = particle[`${name}-${1}`];
                        springMaker(f, t, `${name}-${precision - 1}~${name}-${1}`);
                    }
                }
                else if (['Block', 'Cell', 'Element', 'Grid', 'Phrase', 'Picture', 'Stack'].indexOf(host.type) >= 0) {
    
                }
                else if (host.type === 'Wheel') {
    
                }
    
                let [x, y] = shapeTemplate.get('position');
    
                hub = makeParticle({
    
                    name: `${name}-hub`,
    
                    positionX: x,
                    positionY: y,
                    positionZ: 0,
    
                    velocityX: 0,
                    velocityY: 0,
                    velocityZ: 0,
    
                    historyLength,
                    engine,
                    forces,
    
                    mass,
    
                    fill: art.get('fillStyle'),
                    stroke: art.get('strokeStyle'),
                });
    
                hub.run(0, 0, false);
    
                particleStore.forEach((p, index) => springMaker(p, hub, `${name}-${index}-hub`));
    
                particleStore.push(hub);
            }
        },
    };
  • ¶

    Factory

    const makeNet = function (items) {
        return new Net(items);
    };
    
    constructors.Net = Net;
  • ¶

    Exports

    export {
        makeNet,
    };