UNPKG

scrawl-canvas

Version:
398 lines (294 loc) 11.3 kB
// # Demo Particles 002 // Emitter using artefacts // [Run code](../../demo/particles-002.html) import * as scrawl from '../source/scrawl.js' import { reportSpeed } from './utilities.js'; // #### Scene setup let canvas = scrawl.library.artefact.mycanvas; scrawl.importDomImage('#bunny'); canvas.setBase({ backgroundColor: '#000040', }); // Create entitys that we can use with the particle emitter // + Set various `noXYZ` flags to false in the hope that this will speed up stamp computation times // + Set `visibility` to false to prevent the entity appearing in the scene outside of the emitter's control // + No need to set `fillStyle` and `strokeStyle` colors - we'll do that via the particle emitter and the world object let commonValues = { handle: ['center', 'center'], lineWidth: 2, method: 'fillThenDraw', visibility: false, noUserInteraction: true, noPositionDependencies: true, noFilters: true, noDeltaUpdates: true, }; let wheel = scrawl.makeWheel({ name: 'particle-wheel-entity', radius: 16, startAngle: 20, endAngle: -20, includeCenter: true, // @ts-expect-error }).set(commonValues); let block = scrawl.makeBlock({ name: 'particle-block-entity', dimensions: [30, 20], // @ts-expect-error }).set(commonValues); let star = scrawl.makeStar({ name: 'particle-star-entity', radius1: 18, radius2: 12, points: 5, // @ts-expect-error }).set(commonValues); let picture = scrawl.makePicture({ name: 'particle-image-entity', asset: 'bunny', width: 26, height: 37, copyWidth: '100%', copyHeight: '100%', // @ts-expect-error }).set(commonValues); let phrase = scrawl.makePhrase({ name: 'particle-phrase-entity', text: 'Hello', font: 'bold 40px Garamond, serif', exposeText: false, // @ts-expect-error }).set(commonValues); // #### Particle physics animation scene // Create a World object which we can then assign to the particle emitter let myWorld = scrawl.makeWorld({ name: 'demo-world', tickMultiplier: 2, userAttributes: [ { key: 'strokeStyle', defaultValue: '#b9b5df', }, { key: 'globalAlpha', defaultValue: 0.2, }, ], }); // Create the particle Emitter entity // + Emitter entitys include functionality for assigning randomized fillStyle and strokeStyle colors to the particles they generate. They do this through the use of a Color factory in the emitter. By default the emitter will assign the full range of random colors to each particle (which then remembers its color). // + We can restrict the range of colors generated by setting the minimum and maximum color values on these color factories - see the user interaction code near the end of this file for details. const myemitter = scrawl.makeEmitter({ name: 'use-artefact', world: myWorld, start: ['center', 'center'], historyLength: 20, generationRate: 10, killAfterTime: 5, // As well as killing our particles beyond their lifetime limit (as set in `killAfterTime`) we can also kill particles that move a certain distance beyond their initial position killRadius: 50, killRadiusVariation: 0, // Assign the artefact that we will be using for the particle animation artefact: star, rangeX: 40, rangeFromX: -20, rangeY: 40, rangeFromY: -20, rangeZ: -1, rangeFromZ: -0.2, // Define the stampAction function stampAction: function (artefact, particle, host) { // We will use the (semi-)random `fill` color assigned to the particle when it was generated let {history, fill} = particle; let remaining, scale, roll, start, z; // For the stroke color, we shall give all particles the same color, as defined in our World object // @ts-expect-error let {strokeStyle, globalAlpha} = myWorld; // We will display each particle on the canvas using the entity currently assigned to our emitter's `artefact` attribute history.forEach((p, index) => { // Entitys use Scrawl-canvas Coordinate arrays for their positioning data; we can set the `start` Coordinate using a normal Array containing `[x, y]` data - which we can easily extract from the particle's history arrays like so: [remaining, z, ...start] = p; // We change the size of this instance of the particle based on it's `z coordinate` value, by scaling the artefact. scale = 1 + (z / 3); if (scale > 0) { // We can roll the artefact (for added drama), calculating the `roll` value based on the amount of time left before the particle gets killed. roll = (remaining / 5) * 720; // The artefact's `simpleStamp` function bypasses much of the normal Display cycle functionality; instead it forces the artefact to stamp itself onto the host Cell's <canvas> element immediately // + The first argument is the host Cell object, // + The second argument is a normal JS object containing key:value pairs describing the changes we want to make to the artefact prior to it stamping itself onto the canvas. artefact.simpleStamp(host, { start, scale, roll, globalAlpha, strokeStyle, fillStyle: fill, }); } }); }, }); // #### Scene animation // Function to display frames-per-second data, and other information relevant to the demo const particlenames = scrawl.library.particlenames, particle = scrawl.library.particle; const report = reportSpeed('#reportmessage', function () { // ParticleHistory arrays are not saved in the Scrawl-canvas library; instead we need to count them in each particle let historyCount = 0; particlenames.forEach(n => { let p = particle[n]; if (p) historyCount += p.history.length; }); // @ts-expect-error let kr = parseFloat(killRadius.value), // @ts-expect-error krv = parseFloat(killRadiusVariation.value) / 2; // @ts-expect-error return ` Particles: ${particlenames.length}, generationRate: ${generationRate.value}, historyLength: ${historyLength.value}\n Stamps per display: ${historyCount}\n\n Background color: ${background.value}, World speed (tickMultiplier): ${worldSpeed.value}\n Outline color (strokeStyle): ${outlineColor.value}, Opacity (globalAlpha): ${opacity.value}\n Kill radius: from ${(kr - krv) > 0 ? kr - krv : 0}px to ${kr + krv}px\n Minimum fill color: ${minFill.value}, Maximum fill color: ${maxFill.value}`; }); let mouseCheck = function () { let active = false; return function () { if (canvas.here.active !== active) { active = canvas.here.active; myemitter.set({ lockTo: (active) ? 'mouse' : 'start' }); } }; }(); // Create the Display cycle animation scrawl.makeRender({ name: 'demo-animation', target: canvas, commence: mouseCheck, afterShow: report, }); // #### User interaction // For this demo we will suppress touchmove functionality over the canvas scrawl.addNativeListener('touchmove', (e) => { e.preventDefault(); e.returnValue = false; }, canvas.domElement); // Setting (restricting) the range of colors assigned to particles when they are generated. const setLowColor = function () { const selector = document.querySelector('#min-fill'), colorFactory = myemitter.fillColorFactory; return function () { // @ts-expect-error colorFactory.setMinimumColor(selector.value); } }(); scrawl.addNativeListener(['input', 'change'], setLowColor, '#min-fill'); const setHighColor = function () { const selector = document.querySelector('#max-fill'), colorFactory = myemitter.fillColorFactory; return function () { // @ts-expect-error colorFactory.setMaximumColor(selector.value); } }(); scrawl.addNativeListener(['input', 'change'], setHighColor, '#max-fill'); // Change the artefact to be used in the animation const useArtefact = function () { const selector = document.querySelector('#artefact'); let choice, val; return function () { // @ts-expect-error val = selector.value; choice; switch (val) { case 'star' : choice = star; break; case 'wheel' : choice = wheel; break; case 'block' : choice = block; break; case 'picture' : choice = picture; break; case 'phrase' : choice = phrase; break; } if (choice) { myemitter.set({ artefact: choice, }); } } }(); scrawl.addNativeListener(['input', 'change'], useArtefact, '#artefact'); // Setup other form observer functionality scrawl.observeAndUpdate({ event: ['input', 'change'], origin: '.controlItem', target: myWorld, useNativeListener: true, preventDefault: true, updates: { 'outline-color': ['strokeStyle', 'raw'], 'world-speed': ['tickMultiplier', 'float'], 'opacity': ['globalAlpha', 'float'], }, }); scrawl.observeAndUpdate({ event: ['input', 'change'], origin: '.controlItem', target: myemitter, useNativeListener: true, preventDefault: true, updates: { generationRate: ['generationRate', 'int'], historyLength: ['historyLength', 'int'], 'kill-radius': ['killRadius', 'int'], 'kill-radius-variation': ['killRadiusVariation', 'int'], }, }); scrawl.observeAndUpdate({ event: ['input', 'change'], origin: '.controlItem', target: canvas, useNativeListener: true, preventDefault: true, updates: { background: ['backgroundColor', 'raw'], }, }); const worldSpeed = document.querySelector('#world-speed'), minFill = document.querySelector('#min-fill'), maxFill = document.querySelector('#max-fill'), background = document.querySelector('#background'), outlineColor = document.querySelector('#outline-color'), opacity = document.querySelector('#opacity'), killRadius = document.querySelector('#kill-radius'), killRadiusVariation = document.querySelector('#kill-radius-variation'), historyLength = document.querySelector('#historyLength'), generationRate = document.querySelector('#generationRate'); // @ts-expect-error minFill.value = '#000000'; // @ts-expect-error maxFill.value = '#ffffff'; // @ts-expect-error outlineColor.value = '#F0F8FF'; // @ts-expect-error background.value = '#000040'; // @ts-expect-error worldSpeed.value = 2; // @ts-expect-error opacity.value = 0.2; // @ts-expect-error generationRate.value = 10; // @ts-expect-error historyLength.value = 20; // @ts-expect-error killRadius.value = 50; // @ts-expect-error killRadiusVariation.value = 0; // @ts-expect-error document.querySelector('#artefact').value = 'star'; // #### Development and testing console.log(scrawl.library);