123

Pen Settings

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

You're using npm packages, so we've auto-selected Babel for you here, which we require to process imports and make it all work. If you need to use a different JavaScript preprocessor, remove the packages in the npm tab.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Use npm Packages

We can make npm packages available for you to use in your JavaScript. We use webpack to prepare them and make them available to import. We'll also process your JavaScript with Babel.

⚠️ This feature can only be used by logged in users.

Code Indentation

     

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

HTML Settings

Here you can Sed posuere consectetur est at lobortis. Donec ullamcorper nulla non metus auctor fringilla. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec id elit non mi porta gravida at eget metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

            
              body, html {
  margin: 0;
  overflow: hidden;
}
            
          
!
            
              
let loader = PIXI.loader;
let sprites = [];
let field;
let config = {
  debug: false,
  particals: 100,
  animating: false,
  friction: 50,
  minDistance: 150,
  forceMultiplier: 250,
  degenerate: true
};
let container = document.createElement('div');
document.body.appendChild(container);

let init = function() {
  initiated = true;
  field = new Particlefield(sprites, container);
  field.behaviour = ParticalFieldBehaviour.SPAWNING;
  // field.addGravitywell();

  window.addEventListener('resize', function() {
    config.particals = field.particalsOnStageAtOnce;
  });
  
  // Setting up the dat gui configuration object
  if( config.debug === true ) field.debug = true;
  else config.debug = field.debug;
  if( config.particals == 0 ) config.particals = field.particalsOnStageAtOnce;
  else field.particalsOnStageAtOnce = config.particals;
  if( config.friction == 0 ) config.friction = (1 - field.airfriction) * 1000;
  else field.airfriction = config.friction / 100;
  if( config.minDistance == 0 ) config.minDistance = field.minDistance;
  else field.minDistance = config.minDistance;
  if( config.forceMultiplier == 0 ) config.forceMultiplier = field.accelerationFactor;
  else field.accelerationFactor = config.forceMultiplier;
  field.degenerate = config.degenerate;
}
let i = 1;
while(i <= 18) {
  loader.add('shape'+i, `https://s3-us-west-2.amazonaws.com/s.cdpn.io/982762/Pattern-${('0' + i).substr(-2)}_2x.png`);
  i++;
}
loader.load((loader, resources) => {
  // Create sprites from our resources. These sprites are going to be used as prototypes onlyps -Al
  //
  for(i in resources) {
    sprites.push(resources[i].texture);
  }

  init();
});

















const PPP = 0.00008;
const airfriction = 0.98;
const minDistance = 100;
const accelerationFactor = 5000;

const ParticalFieldBehaviour = {
  LOOPING: 0,
  SPAWNING: 1
}

class Particlefield {
  constructor(sprites, container) {
    this.dimensions = new Vector(window.innerWidth, window.innerHeight);

    // this.renderer = PIXI.autoDetectRenderer(window.innerWidth, window.innerHeight, {
    //   antialias: true,
    //   transparent: true,
    //   autoResize: true
    // });
    this.app = new PIXI.Application(window.innerWidth, window.innerHeight, {
      // antialias: true,
      transparent: true,
      autoResize: true
    });
    this.renderer = this.app.renderer;
    // this.renderer.plugins.interaction.autoPreventDefault = false;
    this.stage = this.app.stage;
    this.forcesContainer = new PIXI.Container();
    this.stage.addChild(this.forcesContainer);

    this.sprites = sprites;

    this.container = container;

    this.renderer.view.id = 'particalfield'
    this.container.appendChild(this.app.view);

    this.particals = [];

    window.addEventListener('resize', this.resize.bind(this));

    this.running = true;

    MouseBehaviour.init(this);

    this.initialise();
  }

  initialise() {
    let particalnum = this.particalsOnStageAtOnce;

    while(particalnum > 0) {

      this.newParticle();

      particalnum--;
    }
  }

  mouseStateSpinner() {
    MouseBehaviour.state = MouseBehaviourStates.STATE_SPINNER;
  }
  mouseStateExploder() {
    MouseBehaviour.state = MouseBehaviourStates.STATE_EXPLODER;
  }

  removeForces() {
    if( this.forcesContainer ) this.stage.removeChild( this.forcesContainer );
    this.forcesContainer = new PIXI.Container();
    this.stage.addChild(this.forcesContainer);
  }
  addForce(force) {
    if(force instanceof BasicForce) {
      force.debug = this.debug;
      this.forcesContainer.addChild(force);
    }
  }
  addAttractor() {
    let force = new Attractor();
    this.addForce(force);
  }
  addRepeller() {
    let force = new Repeller();
    this.addForce(force);
  }
  addGravitywell() {
    let force = new Gravitywell();
    this.addForce(force);
  }
  addSpincycle() {
    let force = new Spincycle();
    this.addForce(force);
  }

  addVelocityField() {
    let vfield = new VelocityField(this);
    this.addForce(vfield);
  }

  newParticle() {
    let p = this.createPartical();
    this.addParticle(p);
    return p;
  }

  createPartical() {
    let partical = new Particle(this);
    partical.sprite = this.sprites[Math.round(Math.random() * this.sprites.length)];
    partical.position = new Vector(Math.random() * this.dimensions.width, Math.random() * this.dimensions.height);
    // partical.position = new Vector(this.dimensions.width / 2, this.dimensions.height / 2);
    let diff = 2;
    partical.velocity = new Vector(-diff + Math.random() * (diff * 2), -diff + Math.random() * (diff * 2));
    // partical.velocity = new Vector(2,2);

    return partical;
  }

  updateParticalField() {
    let particalDifference = this.particalsOnStageAtOnce - this.particlesOnStageNow;

    if(particalDifference > 0) {
      while(particalDifference > 0) {
        this.newParticle();
        particalDifference--;
      }
    } else {
      if( this.degenerate ) {
        while(particalDifference < 0) {
          let randomIndex = Math.floor(Math.random() * this.particlesOnStageNow);
          let partical = this.particals[randomIndex];
          this.removeParticalByIndex(randomIndex, false);
          particalDifference++;
        }
      }
    }

  }

  render() {
    this.renderparticals();

    this.renderer.render(this.stage);

    if(this.running) {
      requestAnimationFrame(this.render.bind(this));
    }
  }

  renderparticals() {
    this.particals.forEach((p) => {
      p.render();

      this.particals.forEach((q) => {
        p.applyOther(q);
      });
    });
  }

  resize() {
    this.dimensions = new Vector(window.innerWidth, window.innerHeight);
    this.renderer.resize(this.dimensions.width,this.dimensions.height);
    this.updateParticalField();
  }

  addParticle(partical) {
    if(partical instanceof Particle) {
      this.particals.push(partical);
      setTimeout(() => {
        this.stage.addChild(partical.sprite);
      }, 1);
    }
  }
  removePartical(partical) {
    try {
      this.particals.forEach((_partical, i) => {
        if(_partical === partical) {
          this.removeParticalByIndex(i);
          throw {}; // equivalent of break
        }
      });
    } catch (e) {
      // null
    }
  }
  removeParticalByIndex(index, spawn = true) {

    let partical = this.particals[index];
    partical.die();
    // this.stage.removeChild(partical.sprite);
    this.particals.splice(index, 1);

    if( spawn ) {
      let difference = this.particals.length - this.particalsOnStageAtOnce;
      if( difference < 0 ) {
        this.newParticle();
      }
    }
  }
  removeAllParticals() {
    this.particals.forEach((partical, i) => {
      this.stage.removeChild(partical.sprite);
    });
    this.particals = [];
  }

  set running(running) {
    this._running = running === true;

    if(this._running) {
      this.render();
    }
  }
  get running() {
    return this._running || false;
  }

  set minDistance(value) {
    if( value > 0 ) this._minDistance = value;
  }
  get minDistance() {
    return this._minDistance || minDistance;
  }

  set accelerationFactor(value) {
    if( value > 0 ) this._accelerationFactor = value;
  }
  get accelerationFactor() {
    return this._accelerationFactor || accelerationFactor;
  }

  get particlesOnStageNow() {
    return this.particals.length;
  }

  set particalsOnStageAtOnce(POSAO) {
    this.particalDensity = POSAO / this.dimensions.area;
  }
  get particalsOnStageAtOnce() {
    return Math.round(this.dimensions.area * this.particalDensity);
  }

  set particalDensity(density) {
    if(density > 0) {
      this._oldDensity = this.particalDensity;
      this._density = density;
      this.updateParticalField();
    }
  }
  get particalDensity() {
    return this._density || PPP;
  }

  set airfriction(friction) {
    if(friction > 0) this._friction = friction;
  }
  get airfriction() {
    return this._friction || airfriction;
  }

  set behaviour(value) {
    this._behaviour = value;
  }
  get behaviour() {
    return this._behaviour || 0;
  }

  set degenerate(value) {
    this._degenerate = (value === true);
  }
  get degenerate() {
    return this._degenerate !== false;
  }

  get forces() {
    let _forces = [];
    this.forcesContainer.children.forEach((force)=> {
      _forces.push(force);
    });
    return _forces;
  }

  set debug(value) {
    this._debug = value === true;

    this.forcesContainer.children.forEach((child)=> {
      child.debug = this.debug;
    });
  }
  get debug() {
    return this._debug === true;
  }
}




const ParticalStates = {
  STATE_NEW: 0,
  STATE_ALIVE: 1,
  STATE_DYING: 2,
  STATE_DEAD: 4
}

const offscreenBuffer = 200;

// Decorate vector with a new inverse function
const inverseStrengthNew = function(v, st) {
  let x = Math.abs(v.x);
  let y = Math.abs(v.y);
  let strX = st - x;
  let strY = st - y;
  if(v.x < 0) {
    strX = 0 - strX;
  }

  if(v.y < 0) {
    strY = 0 - strY;
  }

  return new Vector(strX, strY);
}

class Particle {
  constructor(field) {
    this.field = field

    this.velocity = new Vector(0, 0);
    this.spin = 0;
    this.state = ParticalStates.STATE_NEW;
    this.mass = 10 / 1000;

    setTimeout(() => {
      this.init();
    }, 0);
  }

  init() {
    if(this.state !== ParticalStates.STATE_NEW) return; // return if, for some reason, this partical has been killed before this method is run.

    let alpha = this.sprite.alpha;
    let width = this.sprite.width;
    let height = this.sprite.height;

    this.sprite.alpha = 0;
    this.sprite.width = 1;
    this.sprite.height = 1;

    this.tweenUp = TweenMax.to(this.sprite, 5.5, {
      alpha: alpha,
      width: width,
      height: height,
      onComplete: () => {
        this.state = ParticalStates.STATE_ALIVE;
      }
    });
  }

  render() {
    let forces = this.forces;

    if(this.field.behaviour === ParticalFieldBehaviour.SPAWNING) {
      if( this.state < ParticalStates.STATE_DYING ) {
        if(this.position.x < -offscreenBuffer
        || this.position.x > this.field.dimensions.x + offscreenBuffer
        || this.position.y < -offscreenBuffer
        || this.position.y > this.field.dimensions.y + offscreenBuffer) {
          this.field.removePartical(this);
        }
      } else {
        return;
      }
    } else {

      if( !this.field.degenerate && this.field.particlesOnStageNow > this.field.particalsOnStageAtOnce )
      {
        if( this.state < ParticalStates.STATE_DYING ) {
          if(this.position.x < -offscreenBuffer
          || this.position.x > this.field.dimensions.x + offscreenBuffer
          || this.position.y < -offscreenBuffer
          || this.position.y > this.field.dimensions.y + offscreenBuffer) {
            this.field.removePartical(this);
          }
        } else {
          return;
        }
      }

      if(this.position.x < -offscreenBuffer) {
        this.position.x = this.field.dimensions.x + offscreenBuffer/2;
      } else if(this.position.x > this.field.dimensions.x + offscreenBuffer) {
        this.position.x = -offscreenBuffer/2;
      }
      if(this.position.y < -offscreenBuffer) {
        this.position.y = this.field.dimensions.y + offscreenBuffer/2;
      } else if(this.position.y > this.field.dimensions.y + offscreenBuffer) {
        this.position.y = -offscreenBuffer/2;
      }
    }

    this.position = this.position.add(forces);
    this.spin += forces.angle / 10000;
    this.spin *= this.frictionalConclusion;
    this.rotation += this.spin;
  }

  applyOther(partical) {
    let distance = partical.position.subtractNew(this.position);
    if(distance.length <= this.minDistance) {
      // Inverting the strength of the vector so that the pull is stronger if the vectors are closer together
      let acceleration = inverseStrengthNew(distance, this.minDistance).divideScalar(distance.length).divideScalar(this.accelerationFactor);
      // let acceleration = inverseStrengthNew(distance, this.minDistance);
      // acceleration = acceleration.divide(acceleration);
      this.velocity.subtract(acceleration);
      partical.velocity.add(acceleration);
    }
  }

  die() {
    if( this.tweenUp ) this.tweenUp.kill(); // cleaning up the old tween, if it exists

    this.state = ParticalStates.STATE_DYING;

    TweenMax.to(this.sprite, 0.5, {
      alpha: 0,
      width: 1,
      height: 1 ,
      onComplete: () => {
        this.state = ParticalStates.STATE_DEAD;
        this.sprite.parent.removeChild(this.sprite);
      }
    });
  }

  set sprite(sprite) {
    if(sprite instanceof PIXI.Texture) {
      this._sprite = new PIXI.Container();
      let __sprite = new PIXI.Sprite(sprite);
      this._sprite.addChild(__sprite);
      __sprite.position.x = -25;
      __sprite.position.y = -25;
      __sprite.height = 50;
      __sprite.width = 50;
      __sprite.alpha = 0.2;
    }
  }
  get sprite() {
    if(!this._sprite || !(this._sprite instanceof PIXI.Sprite || this._sprite instanceof PIXI.Container)) {
      let p = new PIXI.Graphics();
      p.beginFill(0x000000);
      p.lineStyle(0);
      p.drawCircle(5, 5, 5);
      p.endFill();
      let t = PIXI.RenderTexture.create(p.width, p.height);
      this.field.renderer.render(p, t);

      this._sprite = new PIXI.Sprite(t);
      this._sprite.alpha = 0.4;
    }

    return this._sprite;
  }

  set minDistance(value) {
    if( value > 0 ) this._minDistance = value;
  }
  get minDistance() {
    return this._minDistance || this.field.minDistance;
  }

  set accelerationFactor(value) {
    if( value > 0 ) this._accelerationFactor = value;
  }
  get accelerationFactor() {
    return this._accelerationFactor || this.field.accelerationFactor;
  }

  get forces() {
    let forces = new Vector(0, 0);

    let _forces = this.field.forces;
    _forces.forEach((force)=> {
      this.velocity.add(force.calculateForce(this));
    });

    this.velocity = this.velocity.multiplyScalar(this.frictionalConclusion);

    forces.add(this.velocity);

    return forces;
  }

  get frictionalConclusion() {
    let friction = 1 - this.field.airfriction * this.mass;
    if( friction > 1 ) friction = 1;
    if( friction < 0 ) friction = 0;
    return friction;
  }

  set position(pos) {
    if(pos instanceof Vector) {
      this._position = pos;
      this.sprite.position = this._position;
    }
  }
  get position() {
    return this._position || new Vector(0,0);
  }

  set velocity(vel) {
    if(vel instanceof Vector) {
      if( vel.length > 8 ) vel.normalise().length = 8; // maximum velocity of 2
      this._velocity = vel;
    }
  }
  get velocity() {
    return this._velocity || new Vector(0,0);
  }

  set rotation(rotation) {
    this.sprite.rotation = rotation;
  }
  get rotation() {
    return this.sprite.rotation;
  }
}




const MouseBehaviourStates = {
  STATE_IDLE: 0,
  STATE_EXPLODER: 1,
  STATE_SPINNER: 2,
  STATE_SUCKER: 4
}

const onMouseMove = function(e) {

  switch (this.state) {
    case MouseBehaviourStates.STATE_EXPLODER:
      for(let i in this.forces) {
        this.forces[i].position.x = e.clientX;
        this.forces[i].position.y = e.clientY;
      }
      break;
    case MouseBehaviourStates.STATE_SPINNER:
      for(let i in this.forces) {
        this.forces[i].position.x = e.clientX;
        this.forces[i].position.y = e.clientY;
      }
      break;
    case MouseBehaviourStates.STATE_SUCKER:
      break;
  }
}
const onMouseDown = function(e) {

  switch (this.state) {
    case MouseBehaviourStates.STATE_EXPLODER:
      break;
    case MouseBehaviourStates.STATE_SPINNER:
      for(let i in this.forces) {
        this.forces[i].isOn = true;
      }
      break;
    case MouseBehaviourStates.STATE_SUCKER:
      break;
  }

}
const onMouseUp = function(e) {

  switch (this.state) {
    case MouseBehaviourStates.STATE_EXPLODER:
      for(let i in this.forces) {
        this.forces[i].isOn = true;
      }
      this.tweens.forEach((tween)=> {
        tween.play(0);
      });
      break;
    case MouseBehaviourStates.STATE_SPINNER:
      for(let i in this.forces) {
        this.forces[i].isOn = false;
      }
      break;
    case MouseBehaviourStates.STATE_SUCKER:
      break;
  }
}

class MouseBehaviour {
  static init(field) {
    this.state = MouseBehaviourStates.STATE_IDLE;
    if( field instanceof Particlefield ) {
      this.field = field;
      this.initialised = true;
      this._onMouseMove = onMouseMove.bind(this);
      this._onMouseDown = onMouseDown.bind(this);
      this._onMouseUp = onMouseUp.bind(this);
    }
  }

  static setupState() {
    if( !this.initialised ) return;

    this._removeListeners();
    if( this.state === MouseBehaviourStates.STATE_IDLE ) {
    } else {
      this._addListeners();

      this.forces = {};
      this.tweens = [];

      switch (this.state) {
        case MouseBehaviourStates.STATE_EXPLODER:

          this.forces.repeller = new Repeller();
          this.forces.repeller.effectRadius = 1000;
          this.forces.repeller.strength = 0.001;
          this.forces.repeller.isOn = false;

          this.tweens.push(new TweenMax.from(this.forces.repeller, 0.3, { effectRadius: 10, strength: 0.05, onComplete: ()=> { this.forces.repeller.isOn = false; } }));

          break;
        case MouseBehaviourStates.STATE_SPINNER:

          this.forces.attractor = new Attractor();
          this.forces.attractor.effectRadius = 700;
          this.forces.attractor.strength = 0.002;
          this.forces.attractor.isOn = false;

          this.forces.repeller = new Repeller();
          this.forces.repeller.effectRadius = 150;
          this.forces.repeller.strength = 0.03;
          this.forces.repeller.isOn = false;

          this.tweens.push(new TweenMax(this.forces.repeller, 1, { effectRadius: 50, repeat: -1, yoyo: true }));

          this.forces.spinner = new Spincycle();
          this.forces.spinner.effectRadius = 600;
          this.forces.spinner.strength = 0.0002;
          this.forces.spinner.isOn = false;

          break;
        case MouseBehaviourStates.STATE_SUCKER:
          break;
      }

      for(let i in this.forces) {
        this.field.addForce(this.forces[i]);
      }
    }
  }

  static _removeListeners() {
    // window.removeEventListener('pointermove', this._onMouseMove);
    // window.removeEventListener('pointerdown', this._onMouseDown);
    // window.removeEventListener('pointerup', this._onMouseUp);
  }
  static _addListeners() {
    if( !this.initialised ) return;

    // window.addEventListener('pointermove', this._onMouseMove);
    // window.addEventListener('pointerdown', this._onMouseDown);
    // window.addEventListener('pointerup', this._onMouseUp);
  }

  static set state(value) {
    if( !this.initialised ) return;

    this.field.removeForces(); // Remove all existing forces first

    switch (value) {
      case MouseBehaviourStates.STATE_IDLE:
        break;
      case MouseBehaviourStates.STATE_EXPLODER:
        break;
      case MouseBehaviourStates.STATE_SPINNER:
        break;
      case MouseBehaviourStates.STATE_SUCKER:
        break;
      default:
        console.warn('MouseBehaviour.state provided ('+value+') is invalid.');
        return;
    }
    this._state = value;
    this.setupState();
  }
  static get state() {
    return this._state || 0;
  }

  static set tweens(value) {
    if(value instanceof Array) {
      if(this._tweens) {
        this._tweens.forEach((tween)=> {
          tween.kill();
        });
      }
      this._tweens = [];
    }
  }
  static get tweens() {
    return this._tweens;
  }
}




class BasicForce extends PIXI.Container {

  constructor() {
    super();
  }

  /**
   * emitEvent - Takes a custom event name and data and emits it to the window.
   *
   * @private
   * @param  {String} eventname The name of the event to emit
   * @param  {Object} data      The data to emit to event.detail
   * @return {undefined}
   */
  emitEvent(eventname, data) {
    if(typeof eventname != 'string')  {
      return;
    }

    var event = null;

    if (window.CustomEvent) {
      event = new CustomEvent(eventname, {detail: data});
    } else {
      event = document.createEvent('CustomEvent');
      event.initCustomEvent(eventname, true, true, data);
    }

    window.dispatchEvent(event);
  }

  calculateForce(particle) {
    if(this.isOn === false) {
      return new Vector(0,0);
    }

    return new Vector(0,0);
  }

  set debug(value) {
    this._debug = value === true;
  }
  get debug() {
    return this._debug === true;
  }

  set isOn(value) {
    this._emitting = value === true;
  }
  get isOn() {
    return this._emitting !== false;
  }
}




class Force extends BasicForce {

  constructor() {
    super();

    this.drawAreaDisplay();
    this.drawControls();

    this._onDragStart = this.onDragStart.bind(this);
    this._onDragEnd = this.onDragEnd.bind(this);
    this._onDragMove = this.onDragMove.bind(this);

    window.addEventListener('forceSelected', (e)=> {
      if( e.detail.force !== this ) {
        this.selected = false;
      }
    })

    this.pos = new Vector(window.innerWidth / 2, window.innerHeight / 2);

    this.dragable = true;
    this.interactive = true;
    this.buttonMode = true;
    setTimeout(()=> {
      this.selected = true;
    }, 0);
  }

  drawAreaDisplay() {
    if( this.areaDisplay ) {
      this.removeChild(this.areaDisplay);
      this.areaDisplay = null;
    }

    if( this.debug ) {
      this.areaDisplay = new PIXI.Graphics();
      this.areaDisplay.beginFill(this.forceColour);
      this.areaDisplay.alpha = 0.1;
      this.areaDisplay.drawCircle(0,0,this.effectRadius);

      this.addChild(this.areaDisplay);
    }
  }

  drawControls() {

  }

  onDragStart(e) {
    let originalEvent = e.data.originalEvent;
    this.dragging = false;
    this.mouseDown = true;
    this.dragStart = new Vector(originalEvent.clientX, originalEvent.clientY);
    this.draggingOffset = new Vector(originalEvent.clientX - this.pos.x, originalEvent.clientY - this.pos.y);
    this.dragData = e.data;

    this.selected = true;
  }
  onDragEnd(e) {
    this.dragging = false;
    this.mouseDown = false;
  }
  onDragMove(e) {
    if( this.mouseDown ) {
      let originalEvent = e.data.originalEvent;
      let dragNow = new Vector(originalEvent.clientX, originalEvent.clientY);
      let distanceV = dragNow.subtractNew(this.dragStart);
      let distance = distanceV.length;

      if(distance > 3) {
        this.dragging = true;
      }

      if(this.dragging) {
        this.pos = new Vector(dragNow.x - this.draggingOffset.x, dragNow.y - this.draggingOffset.y);
      }
    }
  }

  set effectRadius(value) {
    if(typeof value == 'number') {
      this._effectRadius = value;
      this.drawAreaDisplay();
    }
  }
  get effectRadius() {
    return this._effectRadius || 300;
  }

  set dragable(value) {
    this._isDragable = value === true;

    if(this._isDragable === true) {
      this.on('pointerdown', this._onDragStart)
          .on('pointerup', this._onDragEnd)
          .on('pointerupoutside', this._onDragEnd)
          .on('pointermove', this._onDragMove);
    } else {
      this.off('pointerdown', this.onDragStart)
          .off('pointerup', this.onDragEnd)
          .off('pointerupoutside', this.onDragEnd)
          .off('pointermove', this.onDragMove);
    }
  }
  get dragable() {
    return this._isDragable !== false;
  }

  set pos(value) {
    if(value instanceof Vector) {
      this.position.x = value.x;
      this.position.y = value.y;
    }
  }
  get pos() {
    return new Vector(this.position.x, this.position.y);
  }

  get forceColour() {
    return 0x333333;
  }

  set selected(value) {
    let oldSelected = this._selected;
    this._selected = value === true;

    if(oldSelected !== this._selected) {
      if(this._selected === true) {
        if( this.debug ) this.areaDisplay.alpha = 0.3;
        this.emitEvent('forceSelected', { force: this });
      } else {
        if( this.debug ) this.areaDisplay.alpha = 0.04;
        this.emitEvent('forceDeselected', { force: this });
      }
    }
  }
  get selected() {
    return this._selected !== false;
  }

  set strength(value) {
    if(typeof value === 'number') this._strength = value;
  }
  get strength() {
    return this._strength || 0.00001;
  }

  set debug(value) {
    this._debug = value === true;
    this.drawAreaDisplay();
  }
  get debug() {
    return this._debug === true;
  }

  set isOn(value) {
    this._emitting = value === true;
    this.drawAreaDisplay();
  }
  get isOn() {
    return this._emitting !== false;
  }
}




class Gravitywell extends Force {
  constructor() {
    super();

    this.strength = 1.8;
  }

  calculateForce(particle) {
    if(this.isOn === false) {
      return new Vector(0,0);
    }

    let distanceV = this.pos.subtractNew(particle.position);
    let distance = distanceV.length;
    if( distance < this.effectRadius ) {
      // let strength = new Vector(0, 0);
      // // This inverts the strength, making the effect stronger near the centre
      // strength.x = -(this.effectRadius / -distanceV.x);
      // strength.y = -(this.effectRadius / -distanceV.y);

      let strength = distanceV.divideScalar(this.effectRadius).multiplyScalar(this.strength);
      return strength;
    }

    return new Vector(0,0);
  }

  get forceColour() {
    if(this.isOn === false) {
      return 0x666666;
    }
    return 0x0088AA;
  }
}
            
          
!
999px
🕑 One or more of the npm packages you are using needs to be built. You're the first person to ever need it! We're building it right now and your preview will start updating again when it's ready.

Console