- Jump To … +
  ./demo/canvas-001.js ./demo/canvas-002.js ./demo/canvas-003.js ./demo/canvas-004.js ./demo/canvas-005.js ./demo/canvas-006.js ./demo/canvas-007.js ./demo/canvas-008.js ./demo/canvas-009.js ./demo/canvas-010.js ./demo/canvas-011.js ./demo/canvas-012.js ./demo/canvas-013.js ./demo/canvas-014.js ./demo/canvas-015.js ./demo/canvas-015a.js ./demo/canvas-016.js ./demo/canvas-017.js ./demo/canvas-018.js ./demo/canvas-019.js ./demo/canvas-019a.js ./demo/canvas-020.js ./demo/canvas-021.js ./demo/canvas-022.js ./demo/canvas-023.js ./demo/canvas-024.js ./demo/canvas-025.js ./demo/canvas-026.js ./demo/canvas-027.js ./demo/canvas-028.js ./demo/canvas-029.js ./demo/canvas-030.js ./demo/canvas-031.js ./demo/canvas-032.js ./demo/canvas-033.js ./demo/canvas-034.js ./demo/canvas-035.js ./demo/canvas-036.js ./demo/canvas-037.js ./demo/canvas-038.js ./demo/canvas-039.js ./demo/canvas-040.js ./demo/canvas-041.js ./demo/canvas-042.js ./demo/canvas-043.js ./demo/canvas-044.js ./demo/canvas-045.js ./demo/canvas-046.js ./demo/canvas-047.js ./demo/canvas-048.js ./demo/canvas-049.js ./demo/canvas-050.js ./demo/canvas-051.js ./demo/canvas-052.js ./demo/canvas-053.js ./demo/canvas-054.js ./demo/canvas-055.js ./demo/canvas-056.js ./demo/canvas-058.js ./demo/canvas-059.js ./demo/canvas-060.js ./demo/canvas-061.js ./demo/canvas-062.js ./demo/canvas-063.js ./demo/canvas-064.js ./demo/core-001.js ./demo/delaunator-001.js ./demo/delaunator-002.js ./demo/dom-001.js ./demo/dom-002.js ./demo/dom-003.js ./demo/dom-004.js ./demo/dom-005.js ./demo/dom-006.js ./demo/dom-007.js ./demo/dom-008.js ./demo/dom-009.js ./demo/dom-010.js ./demo/dom-011.js ./demo/dom-012.js ./demo/dom-013.js ./demo/dom-015.js ./demo/dom-016.js ./demo/dom-017.js ./demo/dom-018.js ./demo/filters-001.js ./demo/filters-002.js ./demo/filters-002a.js ./demo/filters-003.js ./demo/filters-004.js ./demo/filters-005.js ./demo/filters-006.js ./demo/filters-007.js ./demo/filters-008.js ./demo/filters-009.js ./demo/filters-010.js ./demo/filters-011.js ./demo/filters-012.js ./demo/filters-013.js ./demo/filters-014.js ./demo/filters-015.js ./demo/filters-016.js ./demo/filters-017.js ./demo/filters-018.js ./demo/filters-019.js ./demo/filters-020.js ./demo/filters-021.js ./demo/filters-022.js ./demo/filters-023.js ./demo/filters-024.js ./demo/filters-025.js ./demo/filters-026.js ./demo/filters-027.js ./demo/filters-028.js ./demo/filters-101.js ./demo/filters-102.js ./demo/filters-103.js ./demo/filters-104.js ./demo/filters-105.js ./demo/filters-501.js ./demo/filters-502.js ./demo/filters-503.js ./demo/filters-504.js ./demo/filters-505.js ./demo/mediapipe-001.js ./demo/mediapipe-002.js ./demo/mediapipe-003.js ./demo/modules-001.js ./demo/modules-002.js ./demo/modules-003.js ./demo/modules-004.js ./demo/modules-005.js ./demo/modules-006.js ./demo/packets-001.js ./demo/packets-002.js ./demo/particles-001.js ./demo/particles-002.js ./demo/particles-003.js ./demo/particles-004.js ./demo/particles-005.js ./demo/particles-006.js ./demo/particles-007.js ./demo/particles-008.js ./demo/particles-009.js ./demo/particles-010.js ./demo/particles-011.js ./demo/particles-012.js ./demo/particles-013.js ./demo/particles-014.js ./demo/particles-015.js ./demo/particles-016.js ./demo/rapier-001.js ./demo/snippets-001.js ./demo/snippets-002.js ./demo/snippets-003.js ./demo/snippets-004.js ./demo/snippets-005.js ./demo/snippets-006.js ./demo/tensorflow-001.js ./demo/tensorflow-002.js ./demo/utilities.js

- §

  # Demo Particles 002

  Emitter using artefacts

- §

  Run code

      import * as scrawl from '../source/scrawl.js'

      import { reportSpeed } from './utilities.js';

- §

  #### Scene setup

      const 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

      const commonValues = {
          handle: ['center', 'center'],
          lineWidth: 2,
          method: 'fillThenDraw',
          visibility: false,
          noUserInteraction: true,
          noPositionDependencies: true,
          noFilters: true,
          noDeltaUpdates: true,
      };

      const wheel = scrawl.makeWheel({
          name: 'particle-wheel-entity',
          radius: 16,
          startAngle: 20,
          endAngle: -20,
          includeCenter: true,

- §

  @ts-expect-error

      }).set(commonValues);

      const block = scrawl.makeBlock({
          name: 'particle-block-entity',
          dimensions: [30, 20],

- §

  @ts-expect-error

      }).set(commonValues);

      const star = scrawl.makeStar({
          name: 'particle-star-entity',
          radius1: 18,
          radius2: 12,
          points: 5,

- §

  @ts-expect-error

      }).set(commonValues);

      const picture = scrawl.makePicture({
          name: 'particle-image-entity',
          asset: 'bunny',
          width: 26,
          height: 37,
          copyWidth: '100%',
          copyHeight: '100%',

- §

  @ts-expect-error

      }).set(commonValues);

      const 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

      const 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

              const {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

              const {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 => {

- §

  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 => {

              const p = particle[n];
              if (p) historyCount += p.history.length;
          });

- §

  @ts-expect-error

          const 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}`;
      });

      const 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;

              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.makeUpdater({

          event: ['input', 'change'],
          origin: '.controlItem',

          target: myWorld,

          useNativeListener: true,
          preventDefault: true,

          updates: {

              'outline-color': ['strokeStyle', 'raw'],
              'world-speed': ['tickMultiplier', 'float'],
              'opacity': ['globalAlpha', 'float'],
          },
      });

      scrawl.makeUpdater({

          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.makeUpdater({

          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);