- Jump To … +
  ./source/core/animation-loop.js ./source/core/display-cycle.js ./source/core/document-root-elements.js ./source/core/document.js ./source/core/events.js ./source/core/init.js ./source/core/library.js ./source/core/random-seed.js ./source/core/shared-vars.js ./source/core/snippets.js ./source/core/system-flags.js ./source/core/user-interaction.js ./source/core/utilities.js ./source/factory/action.js ./source/factory/anchor.js ./source/factory/animation.js ./source/factory/array-pool.js ./source/factory/bezier.js ./source/factory/block.js ./source/factory/canvas.js ./source/factory/cell-fragment.js ./source/factory/cell.js ./source/factory/cog.js ./source/factory/color.js ./source/factory/conic-gradient.js ./source/factory/coordinate.js ./source/factory/crescent.js ./source/factory/drag-zone.js ./source/factory/element.js ./source/factory/emitter.js ./source/factory/filter-engine-bluenoise-data.js ./source/factory/filter-engine.js ./source/factory/filter.js ./source/factory/font-attributes.js ./source/factory/gradient.js ./source/factory/grid.js ./source/factory/group.js ./source/factory/image-asset.js ./source/factory/keyboard-zone.js ./source/factory/line-spiral.js ./source/factory/line.js ./source/factory/loom.js ./source/factory/mesh.js ./source/factory/net.js ./source/factory/noise-asset.js ./source/factory/observe-update.js ./source/factory/oval.js ./source/factory/palette.js ./source/factory/particle-force.js ./source/factory/particle-history.js ./source/factory/particle-spring.js ./source/factory/particle-world.js ./source/factory/particle.js ./source/factory/pattern.js ./source/factory/phrase.js ./source/factory/picture.js ./source/factory/polygon.js ./source/factory/polyline.js ./source/factory/quadratic.js ./source/factory/quaternion.js ./source/factory/radial-gradient.js ./source/factory/raw-asset.js ./source/factory/reaction-diffusion-asset.js ./source/factory/rectangle.js ./source/factory/render-animation.js ./source/factory/shape.js ./source/factory/spiral.js ./source/factory/sprite-asset.js ./source/factory/stack.js ./source/factory/star.js ./source/factory/state.js ./source/factory/tetragon.js ./source/factory/ticker.js ./source/factory/tracer.js ./source/factory/tween.js ./source/factory/unstacked-element.js ./source/factory/vector.js ./source/factory/video-asset.js ./source/factory/wheel.js ./source/mixin/anchor.js ./source/mixin/asset-advanced-functionality.js ./source/mixin/asset-consumer.js ./source/mixin/asset.js ./source/mixin/base.js ./source/mixin/cascade.js ./source/mixin/cell-key-functions.js ./source/mixin/delta.js ./source/mixin/display-shape.js ./source/mixin/dom.js ./source/mixin/entity.js ./source/mixin/filter.js ./source/mixin/mimic.js ./source/mixin/path.js ./source/mixin/pattern.js ./source/mixin/pivot.js ./source/mixin/position.js ./source/mixin/shape-basic.js ./source/mixin/shape-curve.js ./source/mixin/shape-path-calculation.js ./source/mixin/styles.js ./source/mixin/tween.js

- §

  # Crescent factory

  Crescent entitys are formed by the intersection of two circles, rendered onto a DOM <canvas> element using the Canvas 2D API’s Path2D interface - specifically the arc() method.

  - Positioning functionality for the Crescent is supplied by the position mixin, while rendering functionality comes from the entity mixin.
  - Crescent dimensions are tied closely to its radius attribute; relative dimensions are calculated using the Crescent’s Cell container’s width.
  - Crescents can use CSS color Strings for their fillStyle and strokeStyle values, alongside Gradient, RadialGradient, Color and Pattern objects.
  - They will also accept Filter objects.
  - They can use Anchor objects for user navigation.
  - They can be rendered to the canvas by including them in a Cell object’s Group.
  - They can be animated directly, or using delta animation, or act as the target for Tween animations.
  - Crescents can be cloned, and killed.
- §

  #### Demos:

  - Canvas-055 - Crescent entity functionality
- §

  #### Imports

      import { constructors } from '../core/library.js';

      import { addStrings, doCreate, mergeOver, xta, xto, Ωempty } from '../core/utilities.js';

      import { releaseCoordinate, requestCoordinate } from './coordinate.js';

      import { releaseCell, requestCell } from './cell-fragment.js';

      import baseMix from '../mixin/base.js';
      import entityMix from '../mixin/entity.js';

      import { _radian, DESTINATION_OUT, ENTITY, T_CRESCENT } from '../core/shared-vars.js';

- §

  #### Crescent constructor

      const Crescent = function (items = Ωempty) {

          if (!xto(items.dimensions, items.width, items.height, items.radius)) items.radius = 5;

          this.entityInit(items);

          return this;
      };

- §

  #### Crescent prototype

      const P = Crescent.prototype = doCreate();
      P.type = T_CRESCENT;
      P.lib = ENTITY;
      P.isArtefact = true;
      P.isAsset = false;

- §

  #### Mixins

  - base
  - entity

      baseMix(P);
      entityMix(P);

- §

  #### Crescent attributes

  - Attributes defined in the base mixin: name.
  - Attributes defined in the position mixin: group, visibility, order, calculateOrder, stampOrder, start, startX, startY, handle, handleX, handleY, offset, offsetX, offsetY, dimensions, width, height, pivoted, mimicked, lockTo, lockXTo, lockYTo, scale, roll, noUserInteraction, noPositionDependencies, noCanvasEngineUpdates, noFilters, noPathUpdates, purge, bringToFrontOnDrag.
  - Attributes defined in the delta mixin: delta, noDeltaUpdates.
  - Attributes defined in the pivot mixin: pivot, pivotCorner, addPivotHandle, addPivotOffset, addPivotRotation.
  - Attributes defined in the mimic mixin: mimic, useMimicDimensions, useMimicScale, useMimicStart, useMimicHandle, useMimicOffset, useMimicRotation, useMimicFlip, addOwnDimensionsToMimic, addOwnScaleToMimic, addOwnStartToMimic, addOwnHandleToMimic, addOwnOffsetToMimic, addOwnRotationToMimic.
  - Attributes defined in the path mixin: path, pathPosition, addPathHandle, addPathOffset, addPathRotation, constantPathSpeed.
  - Attributes defined in the entity mixin: method, pathObject, winding, flipReverse, flipUpend, scaleOutline, lockFillStyleToEntity, lockStrokeStyleToEntity, onEnter, onLeave, onDown, onUp, fillStyle, strokeStyle, globalAlpha, globalCompositeOperation, lineWidth, lineCap, lineJoin, lineDash, lineDashOffset, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, filter.
  - Attributes defined in the anchor mixin: anchor.
  - Attributes defined in the filter mixin: filters, isStencil.

  Note that the `flipUpend` attribute appears to have no effect, though `flipReverse` does work. This is due to the way the crescent is constructed and is thus expected behaviour.

      const defaultAttributes = {

- §

  outerRadius, innerRadius - the circles’ radius measured in Number pixels, or as a String% - `'10%'` - of the Cell’s width

          outerRadius: 20,
          innerRadius: 10,

- §

  displacement - the displacement (rightwards) of the inner circle relative to the outer circle. Measured in positive Number pixels; negative values will be set to `0`

          displacement: 0,

- §

  displayIntersect - a Boolean flag determining which of the two parts of the combined circles to display. Defaults to `false`, which shows the portion of the outer circle not covered by the inner circle; set to `true`, will display the intersection of the inner and outer circles

          displayIntersect: false,
      };
      P.defs = mergeOver(P.defs, defaultAttributes);

- §

  #### Packet management

  No additional packet functionality required

- §

  #### Clone management

  No additional clone functionality required

- §

  #### Kill management

  No additional kill functionality required

- §

  #### Get, Set, deltaSet

      const S = P.setters,
          D = P.deltaSetters;

- §

  width and height (and dimensions) values are largely irrelevant to Crescent entitys; they get used internally purely as part of the Display cycle stamp functionality.

- §

  If they are used to (delta)set the entity’s values then outerRadius will be set to the supplied width value with String% values calculated from the entity’s host’s width, while innerRadius will be set to the supplied height value with String% values calculated from the entity’s host’s height

      S.outerRadius = function (val) {

          if (val != null) {

              this.outerRadius = val;
              this.dirtyDimensions = true;
              this.dirtyFilterIdentifier = true;
          }
      };
      D.outerRadius = function (val) {

          if (val != null) {

              this.outerRadius = addStrings(this.outerRadius, val);
              this.dirtyDimensions = true;
              this.dirtyFilterIdentifier = true;
          }
      };

      S.innerRadius = function (val) {

          if (val != null) {

              this.innerRadius = val;
              this.dirtyDimensions = true;
              this.dirtyFilterIdentifier = true;
          }
      };
      D.innerRadius = function (val) {

          if (val != null) {

              this.innerRadius = addStrings(this.innerRadius, val);
              this.dirtyDimensions = true;
              this.dirtyFilterIdentifier = true;
          }
      };

      S.width = S.outerRadius;
      D.width = D.outerRadius;
      S.height = S.innerRadius;
      D.height = D.innerRadius;

      S.displacement = function (val) {

          if (val != null && val.toFixed && val >= 0) {

              let d = val;

              if (d < 0) d = 0;
              this.displacement = d;

              this.dirtyDimensions = true;
              this.dirtyFilterIdentifier = true;
          }
      };
      D.displacement = function (val) {

          if (val != null && val.toFixed) {

              let d = addStrings(this.displacement, val);

              if (d.toFixed && d < 0) d = 0;

              this.displacement = d;
              this.dirtyDimensions = true;
              this.dirtyFilterIdentifier = true;
          }
      };

      S.displayIntersect = function (val) {

          this.displayIntersect = val;
          this.dirtyPathObject = true;
          this.dirtyFilterIdentifier = true;
      };

- §

  #### Prototype functions

- §

  Dimensions calculations - overwrites mixin/position.js function

      P.cleanDimensionsAdditionalActions = function () {

          const { outerRadius:oR, innerRadius:iR, displacement:disp } = this;

          const host = this.getHost();

          let hostDims;

          if (host) hostDims = (host.currentDimensions) ? host.currentDimensions : [host.w, host.h];
          else hostDims = [300, 150];

          const [w, h] = hostDims;

          this.currentOuterRadius = (oR.substring) ? (parseFloat(oR) / 100) * w : oR;
          this.currentInnerRadius = (iR.substring) ? (parseFloat(iR) / 100) * h : iR;

          this.currentDisplacement = (disp.substring) ? (parseFloat(disp) / 100) * w : disp;

          this.currentDimensions[0] = this.currentDimensions[1] = this.currentOuterRadius * 2;

          this.dirtyPathObject = true;

      };

- §

  `calculateInterception` - internal function to calculate how the two circles interact

      P.calculateInterception = function () {

          if (!xta(this.currentOuterRadius, this.currentInnerRadius, this.currentDisplacement)) this.cleanDimensionsAdditionalActions();

          const { currentOuterRadius:oR, currentInnerRadius:iR, currentDisplacement:disp } = this;

          this.outerCircleStart = 0;
          this.outerCircleEnd = 360 * _radian;
          this.innerCircleStart = 0;
          this.innerCircleEnd = 360 * _radian;

          this.drawOuterCircle = false;
          this.drawDonut = false;

          const dMax = oR + iR,
              dMin = oR - iR;

          const equalCircles = (!dMin) ? true : false;

          if (equalCircles && !disp) this.drawOuterCircle = true;
          else {

              if (disp >= dMax) this.drawOuterCircle = true;
              else if (disp < dMax && disp > dMin) {

                  const cell = requestCell();

                  const {engine, element} = cell;

                  const v = requestCoordinate();

                  let a, b;

- §

  Decided to calculate the start/end angles for each circle through brute force

  - Trigonometry is the proper answer, but I can’t get the equations to stay still and play nicely
  - So instead we draw circles on a canvas and rotate a vector to see when it enters/leaves the circles

      /* eslint-disable-next-line */
                  element.width = element.width;
                  engine.fillStyle = 'black';

                  engine.save();

                  engine.beginPath();
                  engine.arc(0, 0, oR, 0, Math.PI * 2);

                  v.setFromArray([iR, 0]);

                  for (a = 0; a < 360; a += 0.5) {

                      v.rotate(0.5);
                      if(engine.isPointInPath(v[0] + disp, v[1])) break;
                  }

                  engine.restore();
                  engine.save();

                  engine.beginPath();
                  engine.arc(disp, 0, iR, 0, Math.PI * 2);

                  v.setFromArray([oR, 0]);

                  for (b = 0; b < 360; b += 0.5) {
                      v.rotate(0.5);
                      if(!engine.isPointInPath(...v)) break;
                  }

                  engine.restore();

                  this.outerCircleStart = -b * _radian;
                  this.outerCircleEnd = b * _radian;
                  this.innerCircleStart = a * _radian;
                  this.innerCircleEnd = -a * _radian;

                  releaseCoordinate(v);
                  releaseCell(cell);
              }
              else this.drawDonut = true;
          }
      };

- §

  Calculate the Crescent entity’s Path2D object

      P.cleanPathObject = function () {

          this.dirtyPathObject = false;

          if (!this.noPathUpdates || !this.pathObject) {

              this.calculateInterception();

              const { currentStampHandlePosition:handle, currentScale:scale, outerCircleStart:ocs, outerCircleEnd:oce, innerCircleStart:ics, innerCircleEnd:ice, drawOuterCircle, displayIntersect } = this;

              let { currentOuterRadius:oR, currentInnerRadius:iR, currentDisplacement:disp } = this;

              const p = this.pathObject = new Path2D();

              oR *= scale;
              iR *= scale;
              disp *= scale;

              const x = oR - (handle[0] * scale),
                  y = oR - (handle[1] * scale);

              if (drawOuterCircle) {

                  p.arc(x, y, oR, ocs, oce);
                  p.closePath();

                  this.pathObjectOuter = false;
                  this.pathObjectInner = false;
              }
              else {

                  const pOuter = this.pathObjectOuter = new Path2D();
                  const pInner = this.pathObjectInner = new Path2D();

                  if (displayIntersect) p.arc(x, y, oR, ocs, oce);
                  else p.arc(x, y, oR, ocs, oce, true);

                  p.arc(x + disp, y, iR, ics, ice);
                  p.closePath();

                  pOuter.arc(x, y, oR, ocs, oce, true);
                  pOuter.closePath();

                  pInner.arc(x + disp, y, iR, ics, ice);
                  pInner.closePath();
              }
          }
      };

- §

  `draw` - stroke the entity outline with the entity’s `strokeStyle` color, gradient or pattern - including shadow

      P.draw = function (engine) {

          if (!this.drawDonut) engine.stroke(this.pathObject);
          else {
              engine.stroke(this.pathObjectOuter);
              engine.stroke(this.pathObjectInner);
          }
      };

- §

  `fill` - fill the entity with the entity’s `fillStyle` color, gradient or pattern - including shadow

      P.fill = function (engine) {

          engine.fill(this.pathObject, this.winding);
      };

- §

  `drawAndFill` - stamp the entity stroke, then fill, then remove shadow and repeat

      P.drawAndFill = function (engine) {

          if (!this.drawDonut) {

              const p = this.pathObject;

              engine.stroke(p);
              engine.fill(p, this.winding);
              this.currentHost.clearShadow();
              engine.stroke(p);
              engine.fill(p, this.winding);
          }
          else {

              const p = this.pathObject,
                  pOuter = this.pathObjectOuter,
                  pInner = this.pathObjectInner;

              engine.stroke(pOuter);
              engine.stroke(pInner);
              engine.fill(p, this.winding);
              this.currentHost.clearShadow();
              engine.stroke(pOuter);
              engine.stroke(pInner);
              engine.fill(p, this.winding);
          }
      };

- §

  `drawAndFill` - stamp the entity fill, then stroke, then remove shadow and repeat

      P.fillAndDraw = function (engine) {

          if (!this.drawDonut) {

              const p = this.pathObject;

              engine.fill(p, this.winding);
              engine.stroke(p);
              this.currentHost.clearShadow();
              engine.fill(p, this.winding);
              engine.stroke(p);
          }
          else {

              const p = this.pathObject,
                  pOuter = this.pathObjectOuter,
                  pInner = this.pathObjectInner;

              engine.fill(p, this.winding);
              engine.stroke(pOuter);
              engine.stroke(pInner);
              this.currentHost.clearShadow();
              engine.fill(p, this.winding);
              engine.stroke(pOuter);
              engine.stroke(pInner);
          }
      };

- §

  `drawThenFill` - stroke the entity’s outline, then fill it (shadow applied twice)

      P.drawThenFill = function (engine) {

          if (!this.drawDonut) {

              const p = this.pathObject;

              engine.stroke(p);
              engine.fill(p, this.winding);
          }
          else {

              const p = this.pathObject,
                  pOuter = this.pathObjectOuter,
                  pInner = this.pathObjectInner;

              engine.stroke(pOuter);
              engine.stroke(pInner);
              engine.fill(p, this.winding);
          }
      };

- §

  `fillThenDraw` - fill the entity’s outline, then stroke it (shadow applied twice)

      P.fillThenDraw = function (engine) {

          if (!this.drawDonut) {

              const p = this.pathObject;

              engine.fill(p, this.winding);
              engine.stroke(p);
          }
          else {

              const p = this.pathObject,
                  pOuter = this.pathObjectOuter,
                  pInner = this.pathObjectInner;

              engine.fill(p, this.winding);
              engine.stroke(pOuter);
              engine.stroke(pInner);
          }
      };

- §

  `clip` - restrict drawing activities to the entity’s enclosed area

      P.clip = function (engine) {

          engine.clip(this.pathObject, this.winding);
       };

- §

  `clear` - remove everything that would have been covered if the entity had performed fill (including shadow)

      P.clear = function (engine) {

          const gco = engine.globalCompositeOperation;

          engine.globalCompositeOperation = DESTINATION_OUT;
          engine.fill(this.pathObject, this.winding);

          engine.globalCompositeOperation = gco;
      };

- §

  #### Factory

      export const makeCrescent = function (items) {

          if (!items) return false;
          return new Crescent(items);
      };

      constructors.Crescent = Crescent;