import * as scrawl from '../source/scrawl.js'
import { reportSpeed, initializeDomInputs } from './utilities.js';
import * as scrawl from '../source/scrawl.js'
import { reportSpeed, initializeDomInputs } from './utilities.js';
const canvas = scrawl.findCanvas('mycanvas');
Namespacing boilerplate
const namespace = canvas.name;
const name = (n) => `${namespace}-${n}`;
Create a Block entity which covers the entire canvas; this will act as the area in which particles will be generated by the Emitter entity
scrawl.makeBlock({
name: name('field-block'),
dimensions: ['100%', '100%'],
method: 'none',
});
Create a World object which we can then assign to the particle emitter
const myWorld = scrawl.makeWorld({
name: name('my-world'),
tickMultiplier: 2,
userAttributes: [
{
key: 'connectionRadius',
defaultValue: 80,
},
],
});
const myEmitter = scrawl.makeEmitter({
name: name('field-emitter'),
world: myWorld,
generationRate: 100,
For this demo, we need to limit the maximum number of Particles which our Emitter will display at any given time. This is done using the particleCount
attribute.
particleCount: 50,
generateInArea: name('field-block'),
killBeyondCanvas: true,
We will give our Particles a gentle initial velocity
rangeX: 12,
rangeFromX: -6,
rangeY: 8,
rangeFromY: -4,
artefact: scrawl.makeStar({
name: name('particle-star'),
radius1: 4,
radius2: 2,
points: 5,
handle: ['center', 'center'],
fillStyle: 'beige',
strokeStyle: 'gray',
method: 'fillThenDraw',
visibility: false,
noUserInteraction: true,
noPositionDependencies: true,
noFilters: true,
noDeltaUpdates: true,
}),
We use the preAction
function to draw the lines between stars; the opacity of the line connecting two stars will depend on the distance between them.
preAction: function (host) {
/** @ts-expect-error */
const particles = this.particleStore,
engine = host.engine,
/** @ts-expect-error */
radius = this.world.connectionRadius;
engine.save();
engine.setTransform(1, 0, 0, 1, 0, 0);
engine.strokeStyle = 'midnightblue';
for (let i = 0, iz = particles.length; i < iz; i++) {
const fromParticle = particles[i],
fromHistory = fromParticle.history;
if (fromHistory && fromHistory[0]) {
const [ , , fx, fy] = fromHistory[0];
for (let j = i + 1, jz = particles.length; j < jz; j ++) {
const toParticle = particles[j],
toHistory = toParticle.history;
if (toHistory && toHistory[0]) {
const [ , , tx, ty] = toHistory[0];
const test = scrawl.requestVector(fx, fy).vectorSubtractArray([tx, ty]),
mag = test.getMagnitude();
scrawl.releaseVector(test);
if (mag < radius) {
const opacity = (radius - mag) / radius;
engine.globalAlpha = opacity;
engine.beginPath();
engine.moveTo(fx, fy);
engine.lineTo(tx, ty);
engine.stroke();
}
}
}
}
}
engine.restore();
},
The stampAction
function just adds the stars to the scene
stampAction: function (artefact, particle, host) {
const [ , , ...start] = particle.history[0];
artefact.simpleStamp(host, {start});
},
The ‘postAction’ function is similar to the preAction function, but now we are drawing lines between stars and the mouse cursor’s current position
postAction: function (host) {
/** @ts-expect-error */
const particles = this.particleStore,
engine = host.engine,
here = host.here;
if (here.active) {
const {x, y} = here;
engine.save();
engine.setTransform(1, 0, 0, 1, 0, 0);
engine.strokeStyle = 'red';
for (let i = 0, iz = particles.length; i < iz; i++) {
const fromParticle = particles[i],
fromHistory = fromParticle.history;
if (fromHistory && fromHistory[0]) {
const [ , , fx, fy] = fromHistory[0];
const test = scrawl.requestVector(fx, fy).vectorSubtractArray([x, y]),
mag = test.getMagnitude();
scrawl.releaseVector(test);
if (mag < 100) {
const opacity = (100 - mag) / 100;
engine.globalAlpha = opacity;
engine.beginPath();
engine.moveTo(fx, fy);
engine.lineTo(x, y);
engine.stroke();
}
}
}
engine.restore();
}
},
});
Function to display frames-per-second data, and other information relevant to the demo
const { particlenames, particle } = scrawl.library;
const report = reportSpeed('#reportmessage', function () {
let historyCount = 0;
particlenames.forEach(n => {
const p = particle[n];
if (p) historyCount += p.history.length;
});
return `
Particles: ${particlenames.length}
Stamps per display: ${historyCount} (plus all connecting lines)`;
});
Create the Display cycle animation
scrawl.makeRender({
name: name('animation'),
target: canvas,
afterShow: report,
});
scrawl.addNativeListener('touchmove', (e) => {
e.preventDefault();
e.returnValue = false;
}, canvas.domElement);
Setup form observer functionality
const dom = initializeDomInputs([
['input', 'particleCount', '50'],
['input', 'connectionRadius', '80'],
]);
Handle user interaction with the controls
const updateParticleCount = function (e) {
if (e && e.target) myEmitter.set({ particleCount: parseInt(e.target.value, 10) });
};
scrawl.addNativeListener(['input', 'change'], updateParticleCount, dom.particleCount);
const updateConnectionRadius = function (e) {
/** @ts-expect-error */
if (e && e.target) myWorld.set({ connectionRadius: parseInt(e.target.value, 10) });
};
scrawl.addNativeListener(['input', 'change'], updateConnectionRadius, dom.connectionRadius);
console.log(scrawl.library);