• 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
  • §

    Net factory

    The Net 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.

  • §

    Imports

    import { artefact, artefactnames, constructors, entity, particle, world } from '../core/library.js';
    
    import { doCreate, isa_fn, isa_obj, mergeOver, pushUnique, xt, xta, λnull, Ωempty } from '../helper/utilities.js';
    
    import { currentGroup } from './canvas.js';
    import { makeParticle } from './particle.js';
    import { makeSpring } from './particle-spring.js';
    
    import { releaseVector, requestVector } from '../untracked-factory/vector.js';
    
    import baseMix from '../mixin/base.js';
    import entityMix from '../mixin/entity.js';
  • §

    Shared constants

    import { _floor, _isArray, _isFinite, _now, _piDouble, _tick, BLACK, BLANK, ENTITY, EULER, SOURCE_OVER, T_NET, T_PARTICLE, T_POLYLINE, T_WORLD } from '../helper/shared-vars.js';
  • §

    Local constants

    const FILL_STYLE = 'fillStyle',
        HUB_SPOKE = 'hub-spoke',
        POSITION = 'position',
        STROKE_STYLE = 'strokeStyle',
        STRONG_NET = 'strong-net',
        STRONG_SHAPE = 'strong-shape',
        WEAK_NET = 'weak-net',
        WEAK_SHAPE = 'weak-shape';
    
    const HUB_ARTEFACTS_1 = ['Bezier', 'Line', 'Oval', 'Polygon', 'Polyline', 'Quadratic', 'Rectangle', 'Shape', 'Spiral', 'Star', 'Tetragon'];
  • §

    Net constructor

    const Net = function (items = Ωempty) {
    
        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. Scrawl-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 = [];
    
        this.lastUpdated = 0;
        this.lastHitParticle = null;
    
        if (!items.group) items.group = currentGroup;
    
        this.set(items);
    
        return this;
    };
  • §

    Net prototype

    const P = Net.prototype = doCreate();
    P.type = T_NET;
    P.lib = ENTITY;
    P.isArtefact = true;
    P.isAsset = false;
  • §

    Mixins

    baseMix(P);
    entityMix(P);
  • §

    Net attributes

    const 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: BLACK,
  • §
    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: BLACK,
  • §

    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.packetObjects = pushUnique(P.packetObjects, ['world', 'artefact', 'shapeTemplate']);
    P.packetFunctions = pushUnique(P.packetFunctions, ['generate', 'postGenerate', 'stampAction']);
    
    P.finalizePacketOut = function (copy, items) {
    
        const forces = items.forces || this.forces || false;
        if (forces) {
    
            const tempForces = [];
            forces.forEach(f => {
    
                if (f.substring) tempForces.push(f);
                else if (isa_obj(f) && f.name) tempForces.push(f.name);
            });
            copy.forces = tempForces;
        }
    
        const tempParticles = [];
        this.particleStore.forEach(p => tempParticles.push(p.saveAsPacket()));
        copy.particleStore = tempParticles;
    
        return copy;
    };
  • §

    Clone management

    P.postCloneAction = function(clone) {
    
        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 () {
    
        const {particleStore, springs} = this;
    
        let tempArt;
  • §

    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 => {
    
            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 === T_POLYLINE && tempArt.useParticlesAsPins) {
    
                    tempArt.pins.forEach((pin, index) => {
    
                        if (isa_obj(pin) && pin.type === T_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

    const S = P.setters;
    
    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 === T_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;
        this.dirtyFilterIdentifier = true;
    };
    
    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;
        this.dirtyFilterIdentifier = true;
    };
  • §

    Prototype functions

  • §

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

    P.regularStamp = function () {
    
        const {world, artefact:art, particleStore, springs, generate, postGenerate, stampAction, lastUpdated, resetAfterBlur, showSprings, showSpringsColor, showHitRadius, hitRadius, hitRadiusColor} = this;
    
        let globalAlpha = 1,
            globalCompositeOperation = SOURCE_OVER,
            particleFrom, particleTo;
    
        if (this.state) {
            globalAlpha = this.state.globalAlpha;
            globalCompositeOperation = this.state.globalCompositeOperation;
        }
    
        const 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 = _tick;
    
        const now = _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 = _tick;
        }
  • §

    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) {
    
            const 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 = BLANK;
            engine.lineWidth = 1;
            engine.resetTransform();
            engine.beginPath();
    
            springs.forEach(s => {
    
                ({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) {
    
            const 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 = BLANK;
    
            engine.resetTransform();
            engine.beginPath();
    
            particleStore.forEach(p => {
                engine.moveTo(p.position.x, p.position.y);
                engine.arc(p.position.x, p.position.y, hitRadius, 0, _piDouble);
            });
    
            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 = _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 = []) {
    
        this.lastHitParticle = null;
    
        if (!this.particlesAreDraggable) return false;
    
        if (this.noUserInteraction) return false;
    
        const tests = (!_isArray(items)) ?  [items] : items,
            particleStore = this.particleStore;
    
        let res = false,
            tx, ty, i, iz, p;
    
        if (tests.some(test => {
    
            if (_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 (!_isFinite(tx) || !_isFinite(ty)) return false;
    
            const 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)) {
    
            const r = this.checkHitReturn(tx, ty, 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, particle) {
    
        return {
            x,
            y,
            artefact: this,
            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) {
    
        const particle = this.lastHitParticle;
    
        if (xta(items, particle)) {
    
            particle.isBeingDragged = items;
  • §

    The pool vector is requested here, but released in the dropArtefact function

            particle.dragOffset = requestVector(particle.position).vectorSubtract(items);
        }
        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;
  • §

    Release the pool vector requested in the pickupArtefact function

        releaseVector(this.lastHitParticle.dragOffset);
    
        this.lastHitParticle.dragOffset = null;
        this.lastHitParticle = null;
        return this;
    };
  • §

    Pre-defined Net generators

  • §

    springMaker - internal helper function to generate the entity’s Spring objects

    const springMaker = function (particleFrom, particleTo, springName) {
    
        const { springs, springConstant, damperConstant, restLength } = this;
    
        const v = requestVector(particleFrom.position).vectorSubtract(particleTo.position);
        const l = v.getMagnitude();
    
        const s = makeSpring({
    
            name: springName,
    
            particleFrom,
            particleTo,
    
            springConstant,
            damperConstant,
    
            restLength: l * restLength,
        });
    
        springs.push(s);
        releaseVector(v);
    };
    
    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) {
    
            const { particleStore, artefact:art, historyLength, engine, forces, mass, rows, columns, rowDistance, columnDistance, name } = this;
    
            if (host && rows > 0 && columns > 0) {
    
                const [x, y] = this.currentStampPosition;
                const [width, height] = host.currentDimensions;
    
                const deltaR = (rowDistance.substring) ? (parseFloat(rowDistance) / 100) * height : rowDistance;
                const deltaC = (columnDistance.substring) ? (parseFloat(columnDistance) / 100) * width : 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(FILL_STYLE),
                            stroke: art.get(STROKE_STYLE),
                        });
    
                        p.run(0, 0, false);
    
                        particleStore.push(p);
                    }
                }
    
                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.call(this, 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.call(this, 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) {
    
            const { particleStore, artefact:art, historyLength, engine, forces, mass, rows, columns, rowDistance, columnDistance, name } = this;
    
            if (host && rows > 0 && columns > 0) {
    
                const [x, y] = this.currentStampPosition;
                const [width, height] = host.currentDimensions;
    
                const deltaR = (rowDistance.substring) ? (parseFloat(rowDistance) / 100) * height : rowDistance;
                const deltaC = (columnDistance.substring) ? (parseFloat(columnDistance) / 100) * width : 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(FILL_STYLE),
                            stroke: art.get(STROKE_STYLE),
                        });
    
                        p.run(0, 0, false);
    
                        particleStore.push(p);
                    }
                }
    
                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.call(this, 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.call(this, 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.call(this, 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.call(this, 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 () {
    
            const { particleStore, artefact:art, historyLength, engine, forces, mass, name, shapeTemplate, precision, joinTemplateEnds } = this;
    
            let i, p, f, t;
    
            if (shapeTemplate && precision) {
    
                for (i = 0; i < precision; i++) {
    
                    const 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(FILL_STYLE),
                        stroke: art.get(STROKE_STYLE),
                    });
    
                    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.call(this, f, t, `${name}-${i}~${name}-${i + 1}`);
                }
    
                if (joinTemplateEnds) {
    
                    f = particle[`${name}-${precision - 1}`];
                    t = particle[`${name}-${0}`];
                    springMaker.call(this, f, t, `${name}-${precision - 1}~${name}-${0}`);
                }
    
                for (i = 0; i < precision - 2; i++) {
    
                    f = particle[`${name}-${i}`];
                    t = particle[`${name}-${i + 2}`];
                    springMaker.call(this, f, t, `${name}-${i}~${name}-${i + 2}`);
                }
    
                if (joinTemplateEnds) {
    
                    f = particle[`${name}-${precision - 2}`];
                    t = particle[`${name}-${0}`];
                    springMaker.call(this, f, t, `${name}-${precision - 2}~${name}-${0}`);
    
                    f = particle[`${name}-${precision - 1}`];
                    t = particle[`${name}-${1}`];
                    springMaker.call(this, f, t, `${name}-${precision - 1}~${name}-${1}`);
                }
    
                for (i = 0; i < precision - 3; i++) {
    
                    f = particle[`${name}-${i}`];
                    t = particle[`${name}-${i + 3}`];
                    springMaker.call(this, f, t, `${name}-${i}~${name}-${i + 3}`);
                }
    
                if (joinTemplateEnds) {
    
                    f = particle[`${name}-${precision - 3}`];
                    t = particle[`${name}-${0}`];
                    springMaker.call(this, f, t, `${name}-${precision - 3}~${name}-${0}`);
    
                    f = particle[`${name}-${precision - 2}`];
                    t = particle[`${name}-${1}`];
                    springMaker.call(this, f, t, `${name}-${precision - 2}~${name}-${1}`);
    
                    f = particle[`${name}-${precision - 1}`];
                    t = particle[`${name}-${2}`];
                    springMaker.call(this, 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 () {
    
            const { particleStore, artefact:art, historyLength, engine, forces, mass, name, shapeTemplate, precision, joinTemplateEnds } = this;
    
            let i, p, f, t;
    
            if (shapeTemplate && precision) {
    
                for (i = 0; i < precision; i++) {
    
                    const 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(FILL_STYLE),
                        stroke: art.get(STROKE_STYLE),
                    });
    
                    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.call(this, f, t, `${name}-${i}~${name}-${i + 1}`);
                }
    
                if (joinTemplateEnds) {
    
                    f = particle[`${name}-${precision - 1}`];
                    t = particle[`${name}-${0}`];
                    springMaker.call(this, f, t, `${name}-${precision - 1}~${name}-${0}`);
                }
    
                for (i = 0; i < precision - 2; i++) {
    
                    f = particle[`${name}-${i}`];
                    t = particle[`${name}-${i + 2}`];
                    springMaker.call(this, f, t, `${name}-${i}~${name}-${i + 2}`);
                }
    
                if (joinTemplateEnds) {
    
                    f = particle[`${name}-${precision - 2}`];
                    t = particle[`${name}-${0}`];
                    springMaker.call(this, f, t, `${name}-${precision - 2}~${name}-${0}`);
    
                    f = particle[`${name}-${precision - 1}`];
                    t = particle[`${name}-${1}`];
                    springMaker.call(this, f, t, `${name}-${precision - 1}~${name}-${1}`);
                }
    
                for (i = 0; i < precision - 3; i++) {
    
                    f = particle[`${name}-${i}`];
                    t = particle[`${name}-${i + 3}`];
                    springMaker.call(this, f, t, `${name}-${i}~${name}-${i + 3}`);
                }
    
                if (joinTemplateEnds) {
    
                    f = particle[`${name}-${precision - 3}`];
                    t = particle[`${name}-${0}`];
                    springMaker.call(this, f, t, `${name}-${precision - 3}~${name}-${0}`);
    
                    f = particle[`${name}-${precision - 2}`];
                    t = particle[`${name}-${1}`];
                    springMaker.call(this, f, t, `${name}-${precision - 2}~${name}-${1}`);
    
                    f = particle[`${name}-${precision - 1}`];
                    t = particle[`${name}-${2}`];
                    springMaker.call(this, f, t, `${name}-${precision - 1}~${name}-${2}`);
                }
    
                const halfPrecision = _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.call(this, 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 () {
    
            const { shapeTemplate, precision } = this;
    
            if (shapeTemplate && shapeTemplate.type && precision) {
    
                const { particleStore, artefact:art, historyLength, engine, forces, mass, name, joinTemplateEnds } = this;
    
                let i, p, f, t;
    
                if (HUB_ARTEFACTS_1.includes(shapeTemplate.type)) {
  • §

    build the rim

                    for (i = 0; i < precision; i++) {
    
                        const 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(FILL_STYLE),
                            stroke: art.get(STROKE_STYLE),
                        });
    
                        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.call(this, f, t, `${name}-${i}-${i + 1}`);
                    }
    
                    if (joinTemplateEnds) {
    
                        f = particle[`${name}-${precision - 1}`];
                        t = particle[`${name}-${0}`];
                        springMaker.call(this, f, t, `${name}-${precision - 1}-0`);
                    }
    
                    for (i = 0; i < precision - 2; i++) {
    
                        f = particle[`${name}-${i}`];
                        t = particle[`${name}-${i + 2}`];
                        springMaker.call(this, f, t, `${name}-${i}~${name}-${i + 2}`);
                    }
    
                    if (joinTemplateEnds) {
    
                        f = particle[`${name}-${precision - 2}`];
                        t = particle[`${name}-${0}`];
                        springMaker.call(this, f, t, `${name}-${precision - 2}~${name}-${0}`);
    
                        f = particle[`${name}-${precision - 1}`];
                        t = particle[`${name}-${1}`];
                        springMaker.call(this, f, t, `${name}-${precision - 1}~${name}-${1}`);
                    }
                }
    
                const [x, y] = shapeTemplate.get(POSITION);
    
                const hub = makeParticle({
    
                    name: `${name}-hub`,
    
                    positionX: x,
                    positionY: y,
                    positionZ: 0,
    
                    velocityX: 0,
                    velocityY: 0,
                    velocityZ: 0,
    
                    historyLength,
                    engine,
                    forces,
    
                    mass,
    
                    fill: art.get(FILL_STYLE),
                    stroke: art.get(STROKE_STYLE),
                });
    
                hub.run(0, 0, false);
    
                particleStore.forEach((p, index) => springMaker.call(this, p, hub, `${name}-${index}-hub`));
    
                particleStore.push(hub);
            }
        },
    };
  • §

    Factory

    export const makeNet = function (items) {
    
        if (!items) return false;
        return new Net(items);
    };
    
    constructors.Net = Net;