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}`;
For this Demo, we are creating a flag and pinning it to a pole. This is the pole.
scrawl.makeLine({
name: name('flagpole'),
startX: 'center',
startY: 30,
endX: 'center',
endY: 'bottom',
lineWidth: 8,
lineCap: 'round',
strokeStyle: 'brown',
method: 'draw',
})
const myWorld = scrawl.makeWorld({
name: name('my-world'),
tickMultiplier: 2,
userAttributes: [
{
key: 'wind',
defaultValue: 0,
},
],
});
Create a ‘wind’ force; we will update the wind direction/strength as part of the Display cycle
scrawl.makeForce({
name: name('wind'),
/* eslint-disable-next-line */
action: (particle, world, host) => {
particle.load.vectorAdd({
/** @ts-expect-error */
x: world.wind,
y: 0,
});
},
});
Make the wind dynamic
const changeWind = function () {
/** @ts-expect-error */
let newWind = myWorld.wind + Math.random() - 0.5;
if (newWind < -15) newWind = -15;
if (newWind > 15) newWind = 15;
myWorld.set({
/** @ts-expect-error */
wind: newWind,
});
};
Create a Net entity
const myNet = scrawl.makeNet({
name: name('test-net'),
Every net must be associated with a World object. The attribute’s value can be the World object’s String name value, or the object itself
world: myWorld,
The entity’s start
coordinates determine where the first pin will be placed on the canvas
startX: 'center',
startY: 40,
The Net entity comes with four pre-defined generate
functions - we will be testing ‘weak-net’ and ‘strong-net’ in this demo.
generate: 'weak-net',
The postGenerate
function runs immediately after the generate
function has created all of the Net entity’s Particle and Spring objects.
generate
function has defined particles and/or springs that we do not need, we can kill them here postGenerate: function () {
Names for ‘weak-net’ and ‘strong-net’ Particles are consistent: ${Net-entity-name}-${row-number}-${column-number}
const regex = RegExp('.*(-0-0|-4-0|-9-0)$');
/** @ts-expect-error */
this.particleStore.forEach(p => {
if (regex.test(p.name)) {
Change the appearance of the selected Particles, and remove the forces acting on them
p.set({
fill: 'black',
stroke: 'black',
forces: [],
});
Prevent Springs associated with the selected Particles from moving them
/** @ts-expect-error */
this.springs.forEach(s => {
if (s && s.particleFrom && s.particleFrom.name === p.name) {
s.particleFromIsStatic = true;
}
if (s && s.particleTo && s.particleTo.name === p.name) {
s.particleToIsStatic = true;
}
})
}
});
},
We tell the Net entity how many rows and columns of Particles we want it to create
rows: 10,
columns: 14,
The distance between rows and columns can be set using either absolute (px) Number values, or relative % String values
rowDistance: 15,
columnDistance: '5%',
We can get the Net entity to display its springs
showSprings: true,
showSpringsColor: 'green',
Particle physics attributes
mass: 1,
forces: ['gravity', name('wind')],
engine: 'runge-kutta',
damperConstant: 5,
We can assign an artefact that we will be using for the particle animation, or we can define it here as part of the Net factory
artefact: scrawl.makeWheel({
name: name('particle-wheel'),
radius: 3,
handle: ['center', 'center'],
method: 'fillThenDraw',
fillStyle: 'gold',
strokeStyle: 'blue',
visibility: false,
globalAlpha: 1,
noUserInteraction: true,
noPositionDependencies: true,
noFilters: true,
noDeltaUpdates: true,
}),
The stampAction
function describes the steps that our Net will take to draw each of its particles (and springs, hit regions) onto the host canvas screen.
stampAction: function (artefact, particle, host) {
const [ , , ...start] = particle.history[0];
artefact.simpleStamp(host, {
start,
fillStyle: particle.fill,
strokeStyle: particle.stroke,
});
},
});
Function to display frames-per-second data, and other information relevant to the demo
const report = reportSpeed('#reportmessage', function () {
/** @ts-expect-error */
const windSpeed = myWorld.wind.toFixed(2);
return `
Tick multiplier: ${dom.tickMultiplier.value}
Particle mass: ${dom.mass.value}
Rest length multiplier: ${dom.restLength.value}
Wind speed: ${windSpeed}
Spring constant: ${dom.springConstant.value}
Damper constant: ${dom.damperConstant.value}`;
});
Create the Display cycle animation
scrawl.makeRender({
name: name('animation'),
target: canvas,
commence: changeWind,
afterShow: report,
});
const dom = initializeDomInputs([
['input', 'springConstant', '50'],
['input', 'mass', '1'],
['input', 'restLength', '1'],
['input', 'tickMultiplier', '2'],
['input', 'damperConstant', '5'],
['select', 'generate', 0],
['select', 'engine', 2],
]);
Setup form observer functionality
const updateSprings = function (e) {
if (e && e.target && e.target.id) {
if (e.target.id === 'springConstant') myNet.set({ springConstant: parseFloat(e.target.value)});
if (e.target.id === 'damperConstant') myNet.set({ damperConstant: parseFloat(e.target.value)});
if (e.target.id === 'restLength') myNet.set({ restLength: parseFloat(e.target.value)});
if (e.target.id === 'generate') myNet.set({ generate: e.target.value});
if (e.target.id === 'engine') myNet.set({ engine: e.target.value});
if (e.target.id === 'mass') myNet.set({ mass: parseFloat(e.target.value)});
if (e.target.id === 'tickMultiplier') myWorld.set({ tickMultiplier: parseFloat(e.target.value)});
myNet.restart();
}
};
scrawl.addNativeListener(['input', 'change'], updateSprings, '.controlItem');
console.log(scrawl.library);