/** @ts-expect-error */
import Delaunator from 'https://cdn.skypack.dev/delaunator@5.0.0';
/** @ts-expect-error */
import Delaunator from 'https://cdn.skypack.dev/delaunator@5.0.0';
The following functions are used to handle the Delaunay object
const edgesOfTriangle = (t) => [3 * t, 3 * t + 1, 3 * t + 2];
const pointsOfTriangle = (del, t) => {
const { triangles } = del;
return edgesOfTriangle(t).map(e => triangles[e]);
};
const triangleOfEdge = (e) => Math.floor(e / 3);
const nextHalfedge = (e) => (e % 3 === 2) ? e - 2 : e + 1;
const forEachTriangleEdge = (pts, del, cb) => {
const { triangles, halfedges } = del;
const len = triangles.length;
for (let e = 0; e < len; e++) {
if (e > halfedges[e]) {
const p = pts[triangles[e]];
const q = pts[triangles[nextHalfedge(e)]];
cb(e, p, q);
}
}
};
const triangleCenter = (pts, del, t) => {
const vertices = pointsOfTriangle(del, t).map(p => pts[p]);
return circumcenter(vertices[0], vertices[1], vertices[2]);
};
const circumcenter = (a, b, c) => {
if (a && b && c && a.length && b.length && c.length) {
const ad = a[0] * a[0] + a[1] * a[1],
bd = b[0] * b[0] + b[1] * b[1],
cd = c[0] * c[0] + c[1] * c[1];
const D = 2 * (a[0] * (b[1] - c[1]) + b[0] * (c[1] - a[1]) + c[0] * (a[1] - b[1]));
return [
1 / D * (ad * (b[1] - c[1]) + bd * (c[1] - a[1]) + cd * (a[1] - b[1])),
1 / D * (ad * (c[0] - b[0]) + bd * (a[0] - c[0]) + cd * (b[0] - a[0])),
];
}
};
const forEachVoronoiEdge = (pts, del, cb) => {
if (del) {
const { triangles, halfedges } = del;
const len = triangles.length;
for (let e = 0; e < len; e++) {
if (e < halfedges[e]) {
const p = triangleCenter(pts, del, triangleOfEdge(e));
const q = triangleCenter(pts, del, triangleOfEdge(halfedges[e]));
cb(e, p, q);
}
}
}
};
import * as scrawl from '../source/scrawl.js';
import { reportSpeed } 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'),
order: 1,
dimensions: ['100%', '100%'],
method: 'none',
});
scrawl.makeWheel({
name: name('mouse-planet'),
radius: 12,
handle: ['center', 'center'],
fillStyle: 'gold',
lockTo: 'mouse',
});
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: 'coords',
defaultValue: [],
/** @ts-expect-error */
getter: function () { return [].concat(this.coords) },
setter: function (emitter) {
/** @ts-expect-error */
const { coords } = this;
coords.length = 0;
const { particleStore } = emitter;
particleStore.forEach(p => {
const pos = p.position;
coords.push([pos.x, pos.y]);
});
const here = (canvas) ? canvas.here : false;
if (here && here.active) coords.push([here.x, here.y]);
},
},
],
});
let delaunay = false;
const buildDelaunayObject = function (coords) {
return Delaunator.from(coords);
};
scrawl.makeEmitter({
name: name('field-emitter'),
world: myWorld,
generationRate: 5,
particleCount: 80,
start: ['center', 'center'],
killRadius: 500,
killRadiusVariation: 0,
rangeX: 24,
rangeFromX: -12,
rangeY: 24,
rangeFromY: -12,
artefact: scrawl.makeStar({
name: name('particle-star-entity'),
radius1: 8,
radius2: 5,
points: 5,
handle: ['center', 'center'],
fillStyle: 'aliceblue',
strokeStyle: 'orange',
method: 'fillThenDraw',
visibility: false,
noUserInteraction: true,
noPositionDependencies: true,
noFilters: true,
noDeltaUpdates: true,
}),
preAction: function (host) {
generate coords
/** @ts-expect-error */
this.world.set({ coords: this });
/** @ts-expect-error */
const c = this.world.get('coords');
Build a new Delaunay object for each iteration (think of this as a stress test)
if (c.length) delaunay = buildDelaunayObject(c);
if (delaunay) {
const engine = host.engine;
engine.save();
engine.setTransform(1, 0, 0, 1, 0, 0);
engine.lineWidth = 3;
engine.strokeStyle = 'gold';
engine.globalAlpha = 0.4;
engine.beginPath();
forEachTriangleEdge(c, delaunay, (e, p, q) => {
if (p && q) {
engine.moveTo(...p);
engine.lineTo(...q);
}
});
engine.stroke();
engine.strokeStyle = 'lightgreen';
engine.globalAlpha = 1;
engine.beginPath();
forEachVoronoiEdge(c, delaunay, (e, p, q) => {
if (p && q) {
engine.moveTo(...p);
engine.lineTo(...q);
}
});
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});
},
});
scrawl.addNativeListener(['touchmove'], (e) => {
e.preventDefault();
e.returnValue = false;
}, canvas.domElement);
Function to display frames-per-second data, and other information relevant to the demo
const report = reportSpeed('#reportmessage');
Create the Display cycle animation
scrawl.makeRender({
name: name('animation'),
target: canvas,
afterShow: report,
});
console.log(scrawl.library);