{
  "information": {
    "Title": "Rates of Reactions with Concentration of Reactant 1 (Blue), Temperature and Amount and Surface Area of Reactant 2 ",
    "Author": [
      "rytan451",
      "lookang"
    ],
    "AuthorLogo": [
      "./1authorlookangphoto5050.png",
      ""
    ],
    "Password": "",
    "Keywords": "",
    "Abstract": "This simulation illustrates the collision theory and shows how it can be used to explain rates of reaction. \nAmount of Reactant 2, Surface Area of Reactant 2, Temperature and Amount of Reactants (Reactant 1) may all be adjusted before running the simulation to see how your changes affect the rate of reaction.\nReactant 2: Red\nReactant 1: Blue\nProduct: Green",
    "Copyright": "",
    "Level": "",
    "Language": "",
    "Logo": [
      "./rateofreactants/Screenshot 2022-10-17 at 8.55.55 PM (2).png"
    ],
    "RunAlways": "false",
    "ModelTab": "",
    "ModelTabTitle": "",
    "ModelName": "",
    "FixedNavigationBar": "false",
    "CSSFile": "",
    "DetectedFiles": [
      "./1authorlookangphoto5050.png",
      "./rateofreactants/Screenshot 2022-10-17 at 8.55.55 PM (2).png"
    ],
    "AuxiliaryFiles": [
      "./rateofreactants/"
    ],
    "HTMLHead": "\n\n<script async=\"\" src=\"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-0121577198857509\" crossorigin=\"anonymous\"></script>\n<script>\n  window.dataLayer = window.dataLayer || [];\n  function gtag(){dataLayer.push(arguments);}\n  gtag('js', new Date());\n  gtag('config', 'UA-3326007-19');\n</script>\n<script data-ad-client=\"ca-pub-0121577198857509\" async=\"\" src=\"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\"></script>",
    "SaveInXMLFormat": "false",
    "IncludeSource": "true",
    "IncludeLibrary": "true",
    "UglifyJS": "false",
    "PreviewFullModel": "false",
    "UseInterpreter": "true",
    "UseDeltaForODE": "false"
  },
  "description": {
    "pages": [
      {
        "Name": "Intro Page",
        "Active": "true",
        "Internal": "false",
        "Type": "DESCRIPTION_EDITOR",
        "Locale": "_default_",
        "Title": "Intro Page",
        "External": "false",
        "Code": "<html>\n<head></head>\n<body>\n<p> </p><p style=\"margin-top: 0\">      This will be a EJSS-based particle physics simulation    </p><p> </p>\n</body>\n</html>"
      }
    ]
  },
  "model": {
    "variables": {
      "pages": [
        {
          "Name": "Var Table",
          "Active": "true",
          "Internal": "false",
          "Type": "VARIABLE_EDITOR",
          "PageComment": "",
          "Variables": [
            {
              "Name": "particleSystem",
              "Value": "",
              "Type": "Object",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "t",
              "Value": "0",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "xList",
              "Value": "[]",
              "Type": "double",
              "Dimension": "[0]",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "yList",
              "Value": "[]",
              "Type": "double",
              "Dimension": "[0]",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "pixelPosList",
              "Value": "[]",
              "Type": "double",
              "Dimension": "[0]",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "wList",
              "Value": "[]",
              "Type": "double",
              "Dimension": "[0]",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "hList",
              "Value": "[]",
              "Type": "double",
              "Dimension": "[0]",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "outlineColorList",
              "Value": "[]",
              "Type": "double",
              "Dimension": "[0]",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "outlineThicknessList",
              "Value": "[]",
              "Type": "double",
              "Dimension": "[0]",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "drawOutlineList",
              "Value": "[]",
              "Type": "double",
              "Dimension": "[0]",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "fillColorList",
              "Value": "[]",
              "Type": "double",
              "Dimension": "[0]",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "drawFillList",
              "Value": "[]",
              "Type": "double",
              "Dimension": "[0]",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "particleCount",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "init",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "deltaT",
              "Value": "1/60",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            }
          ]
        },
        {
          "Name": "User Configuration",
          "Active": "true",
          "Internal": "false",
          "Type": "VARIABLE_EDITOR",
          "PageComment": "",
          "Variables": [
            {
              "Name": "width",
              "Value": "window.innerWidth*0.95",
              "Type": "double",
              "Dimension": "",
              "Comment": "1024",
              "Domain": "public"
            },
            {
              "Name": "height",
              "Value": "window.innerHeight*0.85",
              "Type": "double",
              "Dimension": "",
              "Comment": "768",
              "Domain": "public"
            },
            {
              "Name": "gravityX",
              "Value": "0",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "gravityY",
              "Value": "0",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "trMessage",
              "Value": "\"\"",
              "Type": "string",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            }
          ]
        },
        {
          "Name": "lookang",
          "Active": "true",
          "Internal": "false",
          "Type": "VARIABLE_EDITOR",
          "PageComment": "",
          "Variables": [
            {
              "Name": "text",
              "Value": "\"\"",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "textSet",
              "Value": "[]",
              "Type": "double",
              "Dimension": "[50]",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "fontb",
              "Value": "\"normal bold 1.5vw \"",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "elementinteracted",
              "Value": "-1",
              "Type": "int",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "idx",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "showtextSet",
              "Value": "",
              "Type": "boolean",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "showvarrowSet",
              "Value": "",
              "Type": "boolean",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "vxList",
              "Value": "[]",
              "Type": "double",
              "Dimension": "[0]",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "vyList",
              "Value": "[]",
              "Type": "double",
              "Dimension": "[0]",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "dvxList",
              "Value": "[]",
              "Type": "double",
              "Dimension": "[0]",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "dvyList",
              "Value": "[]",
              "Type": "double",
              "Dimension": "[0]",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "fast",
              "Value": "",
              "Type": "boolean",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "spd",
              "Value": "20",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            }
          ]
        },
        {
          "Name": "interfaceOptions",
          "Active": "true",
          "Internal": "false",
          "Type": "VARIABLE_EDITOR",
          "PageComment": "",
          "Variables": [
            {
              "Name": "redConcentrationNames",
              "Value": "[\"Lots of red reactant\", \"A little red reactant\"]",
              "Type": "string",
              "Dimension": "[2]",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "temperatureNames",
              "Value": "[\"High temperature\", \"Medium temperature\", \"Low temperature\"]",
              "Type": "string",
              "Dimension": "[3]",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "surfaceAreaNames",
              "Value": "[\"High surface area\", \"Medium surface area\", \"Low surface area\"]",
              "Type": "string",
              "Dimension": "[3]",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "blueConcentrationNames",
              "Value": "[\"High concentration blue reactant\", \"Medium concentration blue reactant\", \"Low concentration blue reactant\"]",
              "Type": "string",
              "Dimension": "[3]",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "redConcentrationSetting",
              "Value": "0",
              "Type": "double",
              "Dimension": "",
              "Comment": "0",
              "Domain": "public"
            },
            {
              "Name": "temperatureSetting",
              "Value": "0",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "surfaceAreaSetting",
              "Value": "0",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "blueConcentrationSetting",
              "Value": "0",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "redConcentrationNumbers",
              "Value": "[12, 5]",
              "Type": "double",
              "Dimension": "[2]",
              "Comment": "[12, 5] change this for red",
              "Domain": "public"
            },
            {
              "Name": "temperatures",
              "Value": "[400, 250, 100]",
              "Type": "double",
              "Dimension": "[3]",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "surfaceAreaNumbers",
              "Value": "[17, 9, 1]",
              "Type": "double",
              "Dimension": "[3]",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "blueConcentrationNumbers",
              "Value": "[144, 64, 25]",
              "Type": "double",
              "Dimension": "[3]",
              "Comment": "[144, 64, 25]",
              "Domain": "public"
            },
            {
              "Name": "",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            }
          ]
        }
      ]
    },
    "initialization": {
      "pages": [
        {
          "Name": "undefined",
          "Active": "false",
          "Internal": "false",
          "Type": "CODE_EDITOR",
          "Comment": "",
          "Code": "if (redConcentrationSetting==undefined){\n  redConcentrationSetting=idx;\n  alert(\"redConcentrationSetting=\"+redConcentrationSetting)\n  }"
        },
        {
          "Name": "User-Editable Particle Definitions",
          "Active": "true",
          "Internal": "false",
          "Type": "CODE_EDITOR",
          "Comment": "",
          "Code": "\ninit = function () {\n  let opts = _view.redConcentration.getProperty(\"SelectedOptions\");  // array of options\n  let option = (opts.length > 0) ? opts[0] : \"\"; // selected option \n  let idx = redConcentrationNames.indexOf(option);\n  //idx = redConcentrationNames.indexOf(option);\n  //alert(option)\n  //idx = 0 if selected\n  //idx = -1 else\n  if (idx !== -1) {\n    redConcentrationSetting = idx;\n  }\n  opts = _view.temperature.getProperty(\"SelectedOptions\");  // array of options\n  option = (opts.length > 0) ? opts[0] : \"\"; // selected option \n  idx = temperatureNames.indexOf(option);\n  if (idx !== -1) {\n    temperatureSetting = idx;\n  }\n  opts = _view.surfaceArea.getProperty(\"SelectedOptions\");  // array of options\n  option = (opts.length > 0) ? opts[0] : \"\"; // selected option \n  idx = surfaceAreaNames.indexOf(option);\n  if (idx !== -1) {\n    surfaceAreaSetting = idx;\n  }\n  opts = _view.blueConcentration.getProperty(\"SelectedOptions\");  // array of options\n  option = (opts.length > 0) ? opts[0] : \"\"; // selected option \n  idx = blueConcentrationNames.indexOf(option);\n  if (idx !== -1) {\n    blueConcentrationSetting = idx;\n  }\n\n  let temperature = temperatures[temperatureSetting];\n  let reactant2Separation = surfaceAreaNumbers[surfaceAreaSetting];\n  let reactant2Radius = 8;\n  let reactant1Radius = 8;\n  // Define the types of particles\n  let r1 = new ParticleType(\"reactant1\");\n  let r2 = new ParticleType(\"reactant2\");\n  let pr = new ParticleType(\"product\");\n\n  r1.setFillColor(\"blue\");\n  r1.setRadius(8);\n  r1.setRestitution(1);\n  r1.setFriction(0);\n\n  r2.setFillColor(\"red\");\n  r2.setRadius(reactant2Radius);\n  r2.setRestitution(1);\n  r2.setDensity(10);\n  r2.setFriction(0);\n\n  pr.setFillColor(\"green\");\n  pr.setRadius(12);\n  pr.setDensity(5);\n  pr.setRestitution(1);\n  pr.setFriction(0);\n\n  // When reactant1 collides with reactant2, reactant1 becomes product and reactant2 gets destroyed\n  r1.setCollisionHandler(\"reactant2\", function (r2Part) {\n    this.vel.scale(this.mass);\n    this.vel.addScaled(r2Part.vel, r2Part.mass);\n    this.changeType(\"product\");\n    this.vel.scale(this.invmass);\n    r2Part.destroy();\n    return false;\n  });\n\n  // Spawn reactant2\n  let nx, ny;\n  nx = ny = redConcentrationNumbers[redConcentrationSetting];\n  for (let x = -(nx - 1) * (reactant2Radius + reactant2Separation); x <= (nx - 1) * (reactant2Radius + reactant2Separation); x += (reactant2Radius + reactant2Separation) * 2) {\n    for (let y = -(ny - 1) * (reactant2Radius + reactant2Separation); y <= (ny - 1) * (reactant2Radius + reactant2Separation); y += (reactant2Radius + reactant2Separation) * 2) {\n      particleSystem.addParticle(new Particle(\"reactant2\", x, y));\n    }\n  }\n\n  // Spawn reactant1 and set initial velocity\n  let v = new Vector(temperature + 30, 0);\n  let hasMult = false;\n  for (let i = 0; i < blueConcentrationNumbers[blueConcentrationSetting]; i++) {\n    let x, y, s;\n    s = 0;\n    do {\n      x = randomRange(-width / 2 + reactant1Radius, width / 2 - + reactant1Radius);\n      y = randomRange(-height / 2 + reactant1Radius, height / 2 - reactant1Radius);\n      s += 1;\n    } while (!particleSystem.isPositionFree(x, y, reactant1Radius + 5) && s < 50);\n    let p = new Particle(\"reactant1\", x, y);\n    if (!hasMult) {\n      hasMult = true;\n      v.scale(p.mass);\n    }\n    p.applyImpulse(v.rotate(Math.random() * 2 * Math.PI));\n    particleSystem.addParticle(p);\n  }\n\n\n\n  particleSystem.setWallEnergy(temperature);\n};"
        },
        {
          "Name": "Post-initialization",
          "Active": "true",
          "Internal": "false",
          "Type": "CODE_EDITOR",
          "Comment": "",
          "Code": "initialize ();"
        },
        {
          "Name": "lookang",
          "Active": "true",
          "Internal": "false",
          "Type": "CODE_EDITOR",
          "Comment": "",
          "Code": "if (_isMobile){\n  //do nothing\n  }\n  \n  else{\n    // copy this into the initialization\n// make the font bigger\n\n_view.plottingPanel.getMessageDecoration(\"TL\").getFont().setFontSize(\"1vw\");\n_view.plottingPanel.getMessageDecoration(\"TR\").getFont().setFontSize(\"1vw\");\n_view.plottingPanel.getMessageDecoration(\"BL\").getFont().setFontSize(\"1vw\");\n_view.plottingPanel.getMessageDecoration(\"BR\").getFont().setFontSize(\"1vw\");\n\n//_view.plottingPanel.getMessageDecoration(\"TL\").getStyle().setFillColor(\"red\");\n    }\n\n"
        },
        {
          "Name": "lookangtextset",
          "Active": "true",
          "Internal": "false",
          "Type": "CODE_EDITOR",
          "Comment": "",
          "Code": "for (var i=0; i<300 /* Iterations */ ; i++) {\n  textSet[i] = i+1;\n}\n\n\n\nif (showtextSet==undefined){\n  showtextSet=false;\n  }\n\nif (showvarrowSet==undefined){\n  showvarrowSet=false;\n  }\n"
        }
      ]
    },
    "evolution": {
      "information": {
        "FPS": "20",
        "SPD": "1",
        "RealTimeVariable": "",
        "Autoplay": "false"
      },
      "pages": [
        {
          "Name": "Evol Page",
          "Active": "true",
          "Internal": "false",
          "Type": "ODE_EDITOR",
          "Comment": "",
          "IndependentVariable": "t",
          "Increment": "0.05",
          "Equations": [
            {
              "state": "",
              "rate": ""
            }
          ],
          "Method": "Euler",
          "AbsoluteTolerance": "0.00001",
          "PreliminaryCode": {
            "Code": "",
            "Comment": "Code to be executed before rate equations are evaluated"
          },
          "EventMaximumStep": "",
          "Events": {
            "pages": []
          },
          "ZenoEffect": {
            "Code": "",
            "Comment": "",
            "StopAfterEffect": "true"
          },
          "AccelerationIndependentOfVelocity": "false",
          "ForceSynchronization": "false",
          "UseBestInterpolation": "false",
          "EstimateFirstStep": "false",
          "MemoryLength": "",
          "InternalStep": "",
          "MaximumStep": "",
          "MaximumNumberOfSteps": "10000",
          "RelativeTolerance": "",
          "DelayList": "",
          "DelayMaximum": "",
          "DelayAddDiscont": "",
          "DelayInitialCondition": {
            "Code": "",
            "Comment": ""
          },
          "DirectIncidenceMatrix": "",
          "Discontinuities": {
            "pages": []
          },
          "ErrorHandling": {
            "pages": []
          }
        }
      ]
    },
    "fixed_relations": {
      "pages": [
        {
          "Name": "fixedRel",
          "Active": "true",
          "Internal": "false",
          "Type": "CODE_EDITOR",
          "Comment": "",
          "Code": "\nif (!_isPaused) { // a way to prevent on Drag and sim running\n  particleSystem.onTimeStep(deltaT); // controls the movements of the particles\n}"
        },
        {
          "Name": "user",
          "Active": "true",
          "Internal": "false",
          "Type": "CODE_EDITOR",
          "Comment": "",
          "Code": ""
        }
      ]
    },
    "custom": {
      "pages": [
        {
          "Name": "PartSim",
          "Active": "false",
          "Internal": "false",
          "Type": "LIBRARY_EDITOR",
          "Comment": "",
          "Code": "if (typeof window[\"WeakRef\"] === \"undefined\") {\n  window.WeakRef = (function (wm) {\n    function WeakRef(target) {\n      wm.set(this, target);\n    }\n    WeakRef.prototype.deref = function () {\n      return wm.get(this);\n    };\n    return WeakRef;\n  })(new WeakMap());\n}\nconst debug = Object.create(null);\ndebug.TRACE = 0;\ndebug.INFO = 1;\ndebug.WARN = 2;\ndebug.ERROR = 3;\ndebug.NONE = 4;\n\ndebug.loggingLevel = debug.TRACE;\ndebug.trace = function(msg) {\n  if (debug.loggingLevel <= debug.TRACE) {\n    console.log(msg);\n  }\n};\ndebug.info = function(msg) {\n  if (debug.loggingLevel <= debug.INFO) {\n    console.info(msg);\n  }\n};\ndebug.warn = function(msg) {\n  if (debug.loggingLevel <= debug.WARN) {\n    console.warn(msg);\n  }\n};\ndebug.error = function(msg) {\n  if (debug.loggingLevel <= debug.ERROR) {\n    console.error(msg);\n  }\n};\n\nfunction Vector(x, y) {\n  if (arguments.length === 0) {\n    x = y = 0;\n  } else if (arguments.length !== 2) {\n    debug.warn(`Vector expects 0 or 2 parameters but ${arguments.length} were given`);\n  }\n  this.x = x;\n  this.y = y;\n}\nVector.prototype.set = function(x, y) {\n  if (arguments.length === 0) {\n    this.x = this.y = 0;\n  } else if (arguments.length === 1 && \"x\" in x && \"y\" in x) {\n    this.x = x.x;\n    this.y = x.y;\n  } else if (arguments.length === 2) {\n    this.x = x;\n    this.y = y;\n  }\n  return this;\n};\nVector.prototype.clone = function() {\n  return new Vector(this.x, this.y);\n};\nVector.prototype.add = function(vector) {\n  if (!(\"x\" in vector && \"y\" in vector)) {\n    throw new TypeError(`x and y are not members of vector`);\n  }\n  this.x += vector.x;\n  this.y += vector.y;\n  return this;\n};\nVector.prototype.sub = function(vector) {\n  if (!(\"x\" in vector && \"y\" in vector)) {\n    throw new TypeError(`x and y are not members of vector`);\n  }\n  this.x -= vector.x;\n  this.y -= vector.y;\n  return this;\n};\nVector.prototype.scale = function(scalar) {\n  this.x *= scalar;\n  this.y *= scalar;\n  return this;\n};\nVector.prototype.dot = function(vector) {\n  if (!(\"x\" in vector && \"y\" in vector)) {\n    throw new TypeError(`x and y are not members of vector`);\n  }\n  return this.x * vector.x + this.y * vector.y;\n};\nVector.prototype.crossZ = function(vector) {\n  // Pretending that both vectors are 3D vectors with z=0\n  // the cross product will be a vector with x=y=0\n  // knowing the z component of this cross product can be very useful\n  if (!(\"x\" in vector && \"y\" in vector)) {\n    throw new TypeError(`x and y are not members of vector`);\n  }\n\n  return this.x * vector.y - this.y * vector.x;\n};\nVector.prototype.sqmag = function() {\n  return this.x * this.x + this.y * this.y;\n};\nVector.prototype.mag = function() {\n  return Math.hypot(this.x, this.y);\n};\nVector.prototype.normalize = function() {\n  return this.scale(1/this.mag());\n};\nVector.prototype.normalized = function(vector) {\n  if (!(arguments.length >= 1 && vector instanceof Vector)) {\n    vector = new Vector();\n  }\n  return vector.set(this).normalize();\n};\nVector.prototype.rotate = function(radians) {\n  let c = Math.cos(radians), s = Math.sin(radians);\n  let x = this.x;\n  this.x = c * this.x - s * this.y;\n  this.y = s * x + c * this.y;\n  return this;\n};\nVector.prototype.addScaled = function(vector, scalar) {\n  if (!(\"x\" in vector && \"y\" in vector)) {\n    throw new TypeError(`x and y are not members of vector`);\n  }\n  this.x += vector.x * scalar;\n  this.y += vector.y * scalar;\n};\nfunction randomRange(min, max) {\n  return min + (max - min) * Math.random();\n}\n\nfunction checkIfIsValidCssColor(color) {\n  return CSS.supports(\"color\", color);\n};\n\nfunction ParticleType(name) {\n  if (name in ParticleType.types) {\n    debug.warn(`Particle type \"${name}\" already exists! Overriding`);\n  }\n  this.name = name;\n  this.minRadius = 16;\n  this.maxRadius = 16;\n  this.minDensity = 1;\n  this.maxDensity = 1;\n  this.isOutlineVisible = true;\n  this.outlineColor = \"black\";\n  this.outlineThickness = 1;\n  this.isFillVisible = true;\n  this.fillColor = \"white\";\n  this.minRestitution = 0.5;\n  this.maxRestitution = 0.5;\n  this.minFriction = 0;\n  this.maxFriction = 0;\n  this.minLinearDamping = 0;\n  this.maxLinearDamping = 0;\n  this.minAngularDamping = 0;\n  this.maxAngularDamping = 0;\n  this.collisionHandlers = Object.create(null);\n  ParticleType.types[name] = this;\n};\nParticleType.types = Object.create(null);\nParticleType.get = function(name) {\n  if (name in ParticleType.types) {\n    return ParticleType.types[name];\n  } else {\n    return null;\n  }\n}\n\nParticleType.prototype.getName = function () {\n  return this.name;\n};\nParticleType.prototype.setRadius = function (value) {\n  if (value < 0) {\n    debug.warn(`setRadius called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.minRadius = value;\n  this.maxRadius = value;\n};\nParticleType.prototype.getRadius = function () {\n  return randomRange(this.minRadius, this.maxRadius);\n};\nParticleType.prototype.setMinRadius = function (value) {\n  if (value < 0) {\n    debug.warn(`setMinRadius called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.minRadius = value;\n  if (this.maxRadius < this.minRadius) {\n    debug.info(`Setting minimum radius to ${value}, which is larger than the type's current maximum radius (${this.maxRadius})`);\n    this.maxRadius = this.minRadius;\n  }\n};\nParticleType.prototype.getMinRadius = function () {\n  return this.minRadius;\n};\nParticleType.prototype.setMaxRadius = function (value) {\n  if (value < 0) {\n    debug.warn(`setMaxRadius called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.maxRadius = value;\n  if (this.minRadius > this.maxRadius) {\n    debug.info(`Setting maximum radius to ${value}, which is smaller than the type's current minimum radius (${this.minRadius})`);\n    this.minRadius = this.maxRadius;\n  }\n};\nParticleType.prototype.getMaxRadius = function () {\n  return this.maxRadius;\n};\nParticleType.prototype.setDensity = function (value) {\n  if (value < 0) {\n    debug.warn(`setDensity called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.minDensity = value;\n  this.maxDensity = value;\n};\nParticleType.prototype.getDensity = function () {\n  return randomRange(this.minDensity, this.maxDensity);\n};\nParticleType.prototype.setMinDensity = function (value) {\n  if (value < 0) {\n    debug.warn(`setMinDensity called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  if (value === 0) {\n    debug.info('Setting minimum density to 0. Objects with density 0 behave as if they had infinite mass');\n  }\n  this.minDensity = value;\n  if (this.maxDensity < this.minDensity) {\n    debug.info(`Setting minimum density to ${value}, which is larger than the type's current maximum density (${this.maxDensity})`);\n    this.maxDensity = this.minDensity;\n  }\n};\nParticleType.prototype.getMinDensity = function () {\n  return this.minDensity;\n};\nParticleType.prototype.setMaxDensity = function (value) {\n  if (value < 0) {\n    debug.warn(`setMaxDensity called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  if (value === 0) {\n    debug.info(\"Setting maximum density to 0. All particles of this type will not be affected by collisions\");\n  }\n  this.maxDensity = value;\n  if (this.minDensity > this.maxDensity) {\n    this.minDensity = this.maxDensity;\n  }\n};\nParticleType.prototype.getMaxDensity = function () {\n  return this.maxDensity;\n};\nParticleType.prototype.setIsOutlineVisible = function (value) {\n  if (value !== true && value !== false) {\n    debug.info(`setIsOutlineVisible called with parameter value=${value}, which is not a boolean. This value is interpreted by JavaScript as a ${value ? \"truthy\" : \"falsy\"} value.`)\n  }\n  this.isOutlineVisible = value;\n};\nParticleType.prototype.getIsOutlineVisible = function () {\n  return this.isOutlineVisible;\n};\nParticleType.prototype.setOutlineColor = function (value) {\n  if (checkIfIsValidCssColor(value)) {\n    this.outlineColor = value;\n  } else {\n    debug.warn(`setOutlineColor called with parameter value=${value}, which is not a valid color! Did nothing instead`);\n  }\n};\nParticleType.prototype.getOutlineColor = function () {\n  return this.outlineColor;\n};\nParticleType.prototype.setOutlineThickness = function (value) {\n  if (value < 0) {\n    debug.warn(`setOutlineThickness called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.outlineThickness = value;\n};\nParticleType.prototype.getOutlineThickness = function () {\n  return this.outlineThickness;\n};\nParticleType.prototype.setIsFillVisible = function (value) {\n  if (value !== true && value !== false) {\n    debug.info(`setIsFillVisible called with parameter value=${value}, which is not a boolean. This value is interpreted by JavaScript as a ${value ? \"truthy\" : \"falsy\"} value.`)\n  }\n  this.isFillVisible = value;\n};\nParticleType.prototype.getIsFillVisible = function () {\n  return this.isFillVisible;\n};\nParticleType.prototype.setFillColor = function (value) {\n  if (checkIfIsValidCssColor(value)) {\n    this.fillColor = value;\n  } else {\n    debug.warn(`setFillColor called with parameter value=${value}, which is not a valid color! Did nothing instead`);\n  }\n};\nParticleType.prototype.getFillColor = function () {\n  return this.fillColor;\n};\nParticleType.prototype.setRestitution = function (value) {\n  if (value < 0) {\n    debug.warn(`setRestitution called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  if (value > 1) {\n    debug.warn(`setRestitution called with parameter value=${value}, which is greater than 1!`);\n    value = 1;\n  }\n  this.minRestitution = value;\n  this.maxRestitution = value;\n};\nParticleType.prototype.getRestitution = function () {\n  return randomRange(this.minRestitution, this.maxRestitution);\n};\nParticleType.prototype.setMinRestitution = function (value) {\n  if (value < 0) {\n    debug.warn(`setMinRestitution called with parameter value=${value}, which is less than 0!`)\n    value = 0;\n  }\n  if (value > 1) {\n    debug.warn(`setMinRestitution called with parameter value=${value}, which is greater than 1!`)\n    value = 1;\n  }\n  this.minRestitution = value;\n  if (this.maxRestitution < this.minRestitution) {\n    debug.info(`Setting minimum restitution to ${value}, which is greater than the type's current maximum restitution`)\n    this.maxRestitution = this.minRestitution;\n  }\n};\nParticleType.prototype.getMinRestitution = function () {\n  return this.minRestitution;\n};\nParticleType.prototype.setMaxRestitution = function (value) {\n  if (value < 0) {\n    debug.warn(`setMaxRestitution called with parameter value=${value}, which is less than 0!`)\n    value = 0;\n  }\n  if (value > 1) {\n    debug.warn(`setMaxRestitution called with parameter value=${value}, which is greater than 1!`)\n    value = 1;\n  }\n  this.maxRestitution = value;\n  if (this.minRestitution > this.maxRestitution) {\n    debug.info(`Setting maximum restitution to ${value}, which is less than the type's current minimum restitution`)\n    this.minRestitution = this.maxRestitution;\n  }\n};\nParticleType.prototype.getMaxRestitution = function () {\n  return this.maxRestitution;\n};\nParticleType.prototype.setFriction = function (value) {\n  if (value < 0) {\n    debug.warn(`setFriction called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  if (value > 1) {\n    debug.warn(`setFriction called with parameter value=${value}, which is greater than 1!`);\n    value = 1;\n  }\n  this.minFriction = value;\n  this.maxFriction = value;\n};\nParticleType.prototype.getFriction = function () {\n  return randomRange(this.minFriction, this.maxFriction);\n};\nParticleType.prototype.setMinFriction = function (value) {\n  if (value < 0) {\n    debug.warn(`setMinFriction called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  if (value > 1) {\n    debug.warn(`setMinFriction called with parameter value=${value}, which is greater than 1!`);\n    value = 1;\n  }\n  this.minFriction = value;\n  if (this.maxFriction < this.minFriction) {\n    debug.info(`Setting minimum friction to ${value}, which is greater than the type's current maximum friction`);\n    this.maxFriction = this.minFriction;\n  }\n};\nParticleType.prototype.getMinFriction = function () {\n  return this.minFriction;\n};\nParticleType.prototype.setMaxFriction = function (value) {\n  if (value < 0) {\n    debug.warn(`setMaxFriction called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  if (value > 1) {\n    debug.warn(`setMaxFriction called with parameter value=${value}, which is greater than 1!`);\n    value = 1;\n  }\n  this.maxFriction = value;\n  if (this.minFriction > this.maxFriction) {\n    debug.info(`Setting maximum friction to ${value}, which is less than the type's current minimum friction`);\n    this.minFriction = this.maxFriction;\n  }\n};\nParticleType.prototype.getMaxFriction = function () {\n  return this.maxFriction;\n};\nParticleType.prototype.setLinearDamping = function (value) {\n  if (value < 0) {\n    debug.warn(`setLinearDamping called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.minLinearDamping = value;\n  this.maxLinearDamping = value;\n};\nParticleType.prototype.getLinearDamping = function () {\n  return randomRange(this.minLinearDamping, this.maxLinearDamping);\n};\nParticleType.prototype.setMinLinearDamping = function (value) {\n  if (value < 0) {\n    debug.warn(`setMinLinearDamping called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.minLinearDamping = value;\n  if (this.maxLinearDamping < this.minLinearDamping) {\n    debug.info(`Setting minimum linear damping to ${value}, which is greater than the type's current maximum linear damping`);\n    this.maxLinearDamping = this.minLinearDamping\n  }\n};\nParticleType.prototype.getMinLinearDamping = function () {\n  return this.minLinearDamping;\n};\nParticleType.prototype.setMaxLinearDamping = function (value) {\n  if (value < 0) {\n    debug.warn(`setMaxLinearDamping called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.maxLinearDamping = value;\n  if (this.minLinearDamping > this.maxLinearDamping) {\n    debug.info(`Setting maximum linear damping to ${value}, which is less than the type's current minimum linear damping`);\n  }\n};\nParticleType.prototype.getMaxLinearDamping = function () {\n  return this.maxLinearDamping;\n};\nParticleType.prototype.setAngularDamping = function (value) {\n  if (value < 0) {\n    debug.warn(`setAngularDamping called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.minAngularDamping = value;\n  this.maxAngularDamping = value;\n};\nParticleType.prototype.getAngularDamping = function () {\n  return randomRange(this.minAngularDamping, this.maxAngularDamping);\n};\nParticleType.prototype.setMinAngularDamping = function (value) {\n  if (value < 0) {\n    debug.warn(`setMinAngularDamping called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.minAngularDamping = value;\n  if (this.maxAngularDamping < this.minAngularDamping) {\n    debug.info(`Setting minimum angular damping to ${value}, which is greater than the type's current maximum angular damping`);\n  }\n};\nParticleType.prototype.getMinAngularDamping = function () {\n  return this.minAngularDamping;\n};\nParticleType.prototype.setMaxAngularDamping = function (value) {\n  if (value < 0) {\n    debug.warn(`setMaxAngularDamping called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.maxAngularDamping = value;\n  if (this.minAngularDamping > this.maxAngularDamping) {\n    debug.info(`Setting maximum angular damping to ${value}, which is less than the type's current minimum angular damping`);\n  }\n};\nParticleType.prototype.getMaxAngularDamping = function () {\n  return this.maxAngularDamping;\n};\n// Return truthy values in any callback to prevent collisions from having physics-based effects\nParticleType.prototype.setCollisionHandler = function (name, callbackOrCallbackArray) {\n  let callbackArray = callbackOrCallbackArray;\n  if (typeof callbackArray === \"function\") {\n    callbackArray = [callbackArray];\n  }\n  this.collisionHandlers[name] = callbackArray;\n};\nParticleType.prototype.addCollisionHandler = function (name, callbackOrCallbackArray) {\n  let callbackArray = callbackOrCallbackArray;\n  if (!(name in this.collisionHandlers)) {\n    this.collisionHandlers[name] = [];\n  }\n  if (typeof callbackArray === \"function\") {\n    callbackArray = [callbackArray];\n  }\n  this.collisionHandlers[name].push(...callbackArray);\n};\nfunction ParticleCollection() {\n  // For now, naively just throw all elements in an array\n  this.__allElements = [];\n  this.index = 0;\n  this.gravity = new Vector(gravityX, gravityY);\n  this.wallEnergy = 0;\n  this.hasWallEnergy = false;\n  this.typeCounts = Object.create(null)\n  if (ParticleCollection.particleSystem !== null) {\n    throw \"Only one singleton ParticleCollection may exist\";\n  }\n  xList = [];\n  yList = [];\n  pixelPosList = [];\n  wList = [];\n  hList = [];\n  pixelSizeList = [];\n  shapeTypeList = [];\n  outlineColorList = [];\n  outlineThicknessList = [];\n  drawOutlineList = [];\n  fillColorList = [];\n  drawFillList = [];\n  particleCount = 0;\n}\nParticleCollection.prototype.getTypeCount = function(typeOrTypeName) {\n  let typeName;\n  if (typeof typeOrTypeName === \"string\" || typeOrTypeName instanceof String) {\n    typeName = typeOrTypeName;\n  } else if (typeOrTypeName instanceof ParticleType) {\n    typeName = typeOrTypeName.getName();\n  } else {\n    debug.error(`The parameter was not a string or ParticleType`);\n    return 0;\n  }\n  if (typeName in this.typeCounts) {\n    return this.typeCounts[typeName];\n  } else {\n    return 0;\n  }\n};\nParticleCollection.prototype.disableWallEnergy = function() {\n  this.hasWallEnergy = false;\n};\nParticleCollection.prototype.setWallEnergy = function(energy) {\n  this.wallEnergy = energy;\n  this.hasWallEnergy = true;\n};\nParticleCollection.particleSystem = null;\nParticleCollection.getCollection = function() {\n  if (ParticleCollection.particleSystem === null) {\n    ParticleCollection.particleSystem = new ParticleCollection();\n  }\n  return ParticleCollection.particleSystem;\n};\nParticleCollection.prototype.registerTypeChange = function(fromType, toType) {\n  let fromTypeName = fromType.getName();\n  let toTypeName = toType.getName();\n  if (fromTypeName in this.typeCounts) {\n    this.typeCounts[fromTypeName] -= 1;\n  } else {\n    debug.error(`Type \"${typeName}\" does not exist! Particle count display may be inaccurate`);\n  }\n  if (!(toTypeName in this.typeCounts)) {\n    this.typeCounts[toTypeName] = 0;\n  }\n  this.typeCounts[toTypeName] += 1;\n};\nParticleCollection.prototype.pruneDeletedElements = function() {\n  for (let i = 0; i < this.__allElements.length; i++) {\n    while (i < this.__allElements.length && this.__allElements[i].isRegisteredForDeletion) {\n      let replacementElement = this.__allElements.pop();\n      let typeName = this.__allElements[i].type.getName();\n      if (typeName in this.typeCounts) {\n        this.typeCounts[typeName] -= 1;\n      } else {\n        debug.error(`Type \"${typeName}\" does not exist! Particle count display may be inaccurate`);\n      }\n      if (i < this.__allElements.length) {\n        this.__allElements[i] = replacementElement;\n      }\n    }\n  }\n};\nParticleCollection.prototype.elementsTouching = function(x, y, radius, output=[]) {\n  output.length = 0; // clear array\n  // Naively\n  let idx = 0;\n  for (let i = 0; i < this.__allElements.length; i++) {\n    let c = this.__allElements[i];\n    let p = c.pos;\n    if ((p.x - x) * (p.x - x) + (p.y - y) * (p.y - y) <= (radius + c.radius) * (radius + c.radius)) {\n      output[idx] = c;\n      idx += 1\n    }\n  }\n  output.length = idx;\n  return output;\n};\nParticleCollection.prototype.isPositionFree = function(x, y, radius) {\n  // Naively\n  for (let i = 0; i < this.__allElements.length; i++) {\n    let c = this.__allElements[i];\n    let p = c.pos;\n    if ((p.x - x) * (p.x - x) + (p.y - y) * (p.y - y) <= (radius + c.radius) * (radius + c.radius)) {\n      return false;\n    }\n  }\n  return true;\n}\nParticleCollection.prototype.addParticle = function(particle) {\n  this.__allElements.push(particle);\n  particle._owner = new WeakRef(this);\n  let typeName = particle.type.getName();\n  if (!(typeName in this.typeCounts)) {\n    this.typeCounts[typeName] = 0;\n  }\n  this.typeCounts[typeName] += 1;\n  particleCount += 1;\n};\nParticleCollection.prototype.spawnFreeParticles = function(particleType, count) {\n  for (let i = 0; i < count; i++) {\n    let tries = 0;\n    let p = new Particle(particleType);\n    do {\n      p.pos.x = randomRange(-width/2 + p.radius, width/2 - p.radius);\n      p.pos.y = randomRange(-height/2 + p.radius, height/2 - p.radius);\n      tries += 1;\n    } while (tries < 100 && !this.isPositionFree(p.pos.x, p.pos.y, p.radius))\n    this.addParticle(p);\n  }\n};\nParticleCollection.prototype.getAllParticles = function() {\n  return this.__allElements;\n};\nParticleCollection.prototype.handleWallCollisions = function(particle) {\n  if (particle.pos.x < particle.radius - width/2) {\n    particle.pos.x = particle.radius - width / 2;\n    if (particle.vel.x < 0) {\n      particle.vel.x = -particle.vel.x * particle.restitution;\n      if (this.hasWallEnergy) {\n        particle.vel.x = (particle.vel.x + this.wallEnergy) / 2;\n      }\n    }\n  }\n  if (particle.pos.x > width/2 - particle.radius) {\n    particle.pos.x = width/2 - particle.radius;\n    if (particle.vel.x > 0) {\n      particle.vel.x = -particle.vel.x * particle.restitution;\n      if (this.hasWallEnergy) {\n        particle.vel.x = (particle.vel.x - this.wallEnergy) / 2;\n      }\n    }\n  }\n  if (particle.pos.y < particle.radius - height/2) {\n    particle.pos.y = particle.radius - height / 2;\n    if (particle.vel.y < 0) {\n      particle.vel.y = -particle.vel.y * particle.restitution;\n      if (this.hasWallEnergy) {\n        particle.vel.y = (particle.vel.y + this.wallEnergy) / 2;\n      }\n    }\n  }\n  if (particle.pos.y > height/2 - particle.radius) {\n    particle.pos.y = height/2 - particle.radius;\n    if (particle.vel.y > 0) {\n      particle.vel.y = -particle.vel.y * particle.restitution;\n      if (this.hasWallEnergy) {\n        particle.vel.y = (particle.vel.y - this.wallEnergy) / 2;\n      }\n    }\n  }\n}\nParticleCollection.prototype.onTimeStep = function(deltaT) {\n  this.pruneDeletedElements();\n  let particles = this.getAllParticles();\n  if (this.index >= particles.length) {\n    this.index = 0;\n  }\n  if (particles.length > 0) {\n    particles[this.index].angle %= 2 * Math.PI;\n    this.index += 1;\n  }\n\n  for (let i = 0; i < particles.length; i++) {\n    particles[i]._handleCollisions();\n  }\n  this.gravity.scale(deltaT);\n  for (let i = 0; i < particles.length; i++) {\n    particles[i].applyImpulse(this.gravity.scale(particles[i].mass));\n    this.gravity.scale(particles[i].invmass);\n    particles[i]._timeStep(deltaT);\n    this.handleWallCollisions(particles[i]);\n  }\n  this.render();\n  this.gravity.scale(1/deltaT);\n};\nParticleCollection.prototype.clear = function() {\n  this.__allElements.length = 0;\n  this.typeCounts = Object.create(null);\n};\nParticleCollection.prototype.render = function() {\n  let particles = this.getAllParticles();\n  particleCount = particles.length;\n  xList.length = particleCount;\n  yList.length = particleCount;\n  pixelPosList.length = particleCount;\n  wList.length = particleCount;\n  hList.length = particleCount;\n  pixelSizeList.length = particleCount;\n  shapeTypeList.length = particleCount;\n  outlineColorList.length = particleCount;\n  outlineThicknessList.length = particleCount;\n  drawOutlineList.length = particleCount;\n  fillColorList.length = particleCount;\n  drawFillList.length = particleCount;\n  for (let i = 0; i < particles.length; i++) {\n    particles[i].render(xList, yList, pixelPosList, wList, hList, pixelSizeList, shapeTypeList, outlineColorList, outlineThicknessList, drawOutlineList, fillColorList, drawFillList, i);\n  }\n};\nfunction Particle(type, x, y) {\n  this.isRegisteredForDeletion = false;\n  \n  this.type = null;\n  this.changeType(type);\n\n  this.pos = new Vector(x, y);\n  this.vel = new Vector();\n  this.angle = 0;\n  this.angleV = 0;\n\n  this._deltaAngleV = 0;\n  this._deltaV = new Vector();\n  this._owner = null;\n  // just to give a distinction to easily decide which particle gets to handle each pair of collisions\n  this._collisionHandlePriority = Math.random();\n};\nParticle.normal = new Vector();\nParticle.forceBuilder = new Vector();\nParticle.prototype.changeType = function(type) {\n  let fromType = this.type;\n\n  if (typeof type === \"string\" || type instanceof String) {\n    this.type = ParticleType.get(type);\n    if (this.type === null) {\n      throw TypeError(\"type did not correspond to an existing particle type\");\n    }\n  } else if (type instanceof ParticleType) {\n    this.type = type;\n  } else {\n    throw TypeError(\"type was not a ParticleType or a type name\");\n  }\n  let owner\n  if (fromType !== null && this._owner !== null && (owner = this._owner.deref()) !== undefined) {\n    owner.registerTypeChange(fromType, this.type);\n  }\n\n  this.radius = this.type.getRadius();\n  this.density = this.type.getDensity();\n  this.restitution = this.type.getRestitution();\n  this.friction = this.type.getFriction();\n  this.linearDamping = this.type.getLinearDamping();\n  this.angularDamping = this.type.getAngularDamping();\n\n  if (this.density === 0) {\n    this.mass = 0;\n    this.invmass = 0;\n    this.massMomentOfInertia = 0;\n    this.invmmi = 0;\n  } else {\n    this.mass = this.density * Math.PI * this.radius * this.radius;\n    this.invmass = 1.0 / this.mass;\n    this.massMomentOfInertia = (this.radius * this.radius * this.mass) * 0.5;\n    this.invmmi = 1/this.massMomentOfInertia;\n  }\n}; \nParticle.prototype.destroy = function () {\n  this.isRegisteredForDeletion = true;\n};\nParticle.prototype.applyImpulse = function (impulse, offset = null) {\n  // Applies an impulse at an offset relative to the center of the particle\n  this._deltaV.addScaled(impulse, this.invmass);\n  if (offset !== null) {\n    this.applyMoment(offset.crossZ(impulse));\n  }\n};\nParticle.prototype.applyMoment = function (moment) {\n  this._deltaAngleV += moment * this.invmmi;\n};\nParticle.prototype._handleCollisions = function (particleCollection=null) {\n  if (this.isRegisteredForDeletion) {\n    return;\n  }\n  if (particleCollection === null) {\n    particleCollection = this._owner.deref();\n  }\n  if (particleCollection !== undefined) {\n    let toCollideWith = particleCollection.elementsTouching(this.pos.x, this.pos.y, this.radius);\n    for (let i = 0; i < toCollideWith.length; i++) {\n      let other = toCollideWith[i];\n      // The first condition excludes collisions with self\n      if (this === other) continue;\n      // The second condition ensures that the types' collision handlers are called in a consistent order\n      if (this.type.getName() < other.type.getName()) continue;\n      // The third condition just makes sure that only one particle will handle collision in each pair\n      if (this.type.getName() === other.type.getName() && this._collisionHandlePriority < other._collisionHandlePriority) continue;\n      // The fourth option is to skip processing deleted particles\n      if (other.isRegisteredForDeletion) continue;\n      \n      let willIgnoreCollision = this._runCollisionHandlersWith(other);\n      if (!willIgnoreCollision) {\n        if (this.pos.x === other.pos.x && this.pos.y === other.pos.y) {\n          this.pos.addScaled(this.vel, -1/this.vel.mag());\n        }\n        this._resolveCollisionWith(other);\n      }\n    }\n  }\n};\nParticle.prototype._runCollisionHandlersWith = function (particle) {\n  let willIgnoreCollision = false;\n  if (particle.type.getName() in this.type.collisionHandlers) {\n    let handlers = this.type.collisionHandlers[particle.type.getName()], len = handlers.length;\n    for (let i = 0; i < len; i++) {\n      if (handlers[i].call(this, particle)) {\n        willIgnoreCollision = true;\n      }\n    }\n  }\n  if (this.type.getName() in particle.type.collisionHandlers) {\n    let handlers = particle.type.collisionHandlers[this.type.getName()], len = handlers.length;\n    for (let i = 0; i < len; i++) {\n      if (handlers[i].call(particle, this)) {\n        willIgnoreCollision = true;\n      }\n    }\n  }\n  return willIgnoreCollision;\n}\nParticle.prototype._resolveCollisionWith = function (particle) {\n  Particle.normal.set(particle.pos).sub(this.pos);\n  let penetration = this.radius + particle.radius - Particle.normal.mag();\n  Particle.normal.normalize();\n  Particle.forceBuilder.set(particle.vel).sub(this.vel); // Start with the velocity of particle relative to self\n  let velApart = Particle.normal.dot(Particle.forceBuilder);\n  Particle.forceBuilder.addScaled(Particle.normal, velApart); // It's now the tangental velocity\n\n  this._separateIfOverlapping(particle, penetration);\n  if (velApart > 0) return;\n  let coefficientOfRestitution = Math.min(this.restitution, particle.restitution);\n  let friction = Math.max(this.friction, particle.friction);\n\n  Particle.forceBuilder.scale(friction);\n  Particle.forceBuilder.addScaled(Particle.normal, coefficientOfRestitution * velApart);\n  Particle.forceBuilder.scale(Math.min(this.mass, particle.mass));\n  \n  this.applyImpulse(Particle.forceBuilder, Particle.normal.scale(this.radius));\n  Particle.normal.scale(1/this.radius);\n  Particle.forceBuilder.scale(-1);\n  particle.applyImpulse(Particle.forceBuilder, Particle.normal.scale(-particle.radius));\n};\nParticle.prototype._separateIfOverlapping = function (particle, penetration) {\n  const separateAmount = 0.9;\n  const activationAmount = 0.01;\n  \n  if (penetration > activationAmount) {\n    let massRatio = particle.mass / (this.mass + particle.mass);\n    this.pos.addScaled(Particle.normal, -penetration * separateAmount * massRatio);\n    particle.pos.addScaled(Particle.normal, penetration * separateAmount * (1 - massRatio));\n  }\n};\nParticle.prototype._timeStep = function (deltaT) {\n  this.vel.add(this._deltaV);\n  this.angleV += this._deltaAngleV;\n\n  this._deltaV.set();\n  this._deltaAngleV = 0;\n\n  this.pos.addScaled(this.vel, deltaT);\n  this.angle += this.angleV * deltaT;\n\n  this.vel.scale(Math.pow(1.0 - this.linearDamping, deltaT));\n  this.angleV *= Math.pow(1.0 - this.angularDamping, deltaT);\n};\nParticle.prototype.render = function(xList, yList, pixelPosList, wList, hList, pixelSizeList, shapeTypeList, outlineColorList, outlineThicknessList, drawOutlineList, fillColorList, drawFillList, index=-1) {\n  if (index === -1) {\n    index = xList.length;\n  }\n  xList[index] = this.pos.x;\n  yList[index] = this.pos.y;\n  pixelPosList[index] = true;\n  wList[index] = this.radius * 2;\n  hList[index] = this.radius * 2;\n  pixelSizeList[index] = true;\n  shapeTypeList[index] = \"ELLIPSE\";\n  outlineColorList[index] = this.type.getOutlineColor();\n  outlineThicknessList[index] = this.type.getOutlineThickness();\n  drawOutlineList[index] = this.type.getIsOutlineVisible();\n  fillColorList[index] = this.type.getFillColor();\n  drawFillList[index] = this.type.getIsFillVisible();\n};\n"
        },
        {
          "Name": "fullscreen",
          "Active": "true",
          "Internal": "false",
          "Type": "LIBRARY_EDITOR",
          "Comment": "",
          "Code": "\n\n//https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Using_full_screen_mode\n// does not work for iOS \n/*jslint browser:true */\nfunction toggleFullScreen() {\n  if (!document.fullscreenElement &&    // alternative standard method\n      !document.mozFullScreenElement && !document.webkitFullscreenElement && !document.msFullscreenElement ) {  // current working methods\n    if (document.documentElement.requestFullscreen) {\n      document.documentElement.requestFullscreen();\n    } else if (document.documentElement.msRequestFullscreen) {\n      document.documentElement.msRequestFullscreen();\n    } else if (document.documentElement.mozRequestFullScreen) {\n      document.documentElement.mozRequestFullScreen();\n    } else if (document.documentElement.webkitRequestFullscreen) {\n      document.documentElement.webkitRequestFullscreen();\n    }\n  } else {\n    if (document.exitFullscreen) {\n      document.exitFullscreen();\n    } else if (document.msExitFullscreen) {\n      document.msExitFullscreen();\n    } else if (document.mozCancelFullScreen) {\n      document.mozCancelFullScreen();\n    } else if (document.webkitExitFullscreen) {\n      document.webkitExitFullscreen();\n    }\n  }\n}"
        },
        {
          "Name": "PartSim from StatesofMatter",
          "Active": "false",
          "Internal": "false",
          "Type": "LIBRARY_EDITOR",
          "Comment": "",
          "Code": "if (typeof window[\"WeakRef\"] === \"undefined\") {\n  window.WeakRef = (function (wm) {\n    function WeakRef(target) {\n      wm.set(this, target);\n    }\n    WeakRef.prototype.deref = function () {\n      return wm.get(this);\n    };\n    return WeakRef;\n  })(new WeakMap());\n}\nconst debug = Object.create(null);\ndebug.TRACE = 0;\ndebug.INFO = 1;\ndebug.WARN = 2;\ndebug.ERROR = 3;\ndebug.NONE = 4;\n\ndebug.loggingLevel = debug.TRACE;\ndebug.trace = function(msg) {\n  if (debug.loggingLevel <= debug.TRACE) {\n    console.log(msg);\n  }\n};\ndebug.info = function(msg) {\n  if (debug.loggingLevel <= debug.INFO) {\n    console.info(msg);\n  }\n};\ndebug.warn = function(msg) {\n  if (debug.loggingLevel <= debug.WARN) {\n    console.warn(msg);\n  }\n};\ndebug.error = function(msg) {\n  if (debug.loggingLevel <= debug.ERROR) {\n    console.error(msg);\n  }\n};\n\nfunction Vector(x, y) {\n  if (arguments.length === 0) {\n    x = y = 0;\n  } else if (arguments.length !== 2) {\n    debug.warn(`Vector expects 0 or 2 parameters but ${arguments.length} were given`);\n  }\n  this.x = x;\n  this.y = y;\n}\nVector.prototype.set = function(x, y) {\n  if (arguments.length === 0) {\n    this.x = this.y = 0;\n  } else if (arguments.length === 1 && \"x\" in x && \"y\" in x) {\n    this.x = x.x;\n    this.y = x.y;\n  } else if (arguments.length === 2) {\n    this.x = x;\n    this.y = y;\n  }\n  return this;\n};\nVector.prototype.clone = function() {\n  return new Vector(this.x, this.y);\n};\nVector.prototype.add = function(vector) {\n  if (!(\"x\" in vector && \"y\" in vector)) {\n    throw new TypeError(`x and y are not members of vector`);\n  }\n  this.x += vector.x;\n  this.y += vector.y;\n  return this;\n};\nVector.prototype.sub = function(vector) {\n  if (!(\"x\" in vector && \"y\" in vector)) {\n    throw new TypeError(`x and y are not members of vector`);\n  }\n  this.x -= vector.x;\n  this.y -= vector.y;\n  return this;\n};\nVector.prototype.scale = function(scalar) {\n  this.x *= scalar;\n  this.y *= scalar;\n  return this;\n};\nVector.prototype.dot = function(vector) {\n  if (!(\"x\" in vector && \"y\" in vector)) {\n    throw new TypeError(`x and y are not members of vector`);\n  }\n  return this.x * vector.x + this.y * vector.y;\n};\nVector.prototype.crossZ = function(vector) {\n  // Pretending that both vectors are 3D vectors with z=0\n  // the cross product will be a vector with x=y=0\n  // knowing the z component of this cross product can be very useful\n  if (!(\"x\" in vector && \"y\" in vector)) {\n    throw new TypeError(`x and y are not members of vector`);\n  }\n\n  return this.x * vector.y - this.y * vector.x;\n};\nVector.prototype.sqmag = function() {\n  return this.x * this.x + this.y * this.y;\n};\nVector.prototype.mag = function() {\n  return Math.hypot(this.x, this.y);\n};\nVector.prototype.normalize = function() {\n  return this.scale(1/this.mag());\n};\nVector.prototype.normalized = function(vector) {\n  if (!(arguments.length >= 1 && vector instanceof Vector)) {\n    vector = new Vector();\n  }\n  return vector.set(this).normalize();\n};\nVector.prototype.rotate = function(radians) {\n  let c = Math.cos(radians), s = Math.sin(radians);\n  let x = this.x;\n  this.x = c * this.x - s * this.y;\n  this.y = s * x + c * this.y;\n  return this;\n};\nVector.prototype.addScaled = function(vector, scalar) {\n  if (!(\"x\" in vector && \"y\" in vector)) {\n    throw new TypeError(`x and y are not members of vector`);\n  }\n  this.x += vector.x * scalar;\n  this.y += vector.y * scalar;\n};\nfunction randomRange(min, max) {\n  return min + (max - min) * Math.random();\n}\n\nfunction checkIfIsValidCssColor(color) {\n  return CSS.supports(\"color\", color);\n};\n\nfunction ParticleType(name) {\n  if (name in ParticleType.types) {\n    debug.warn(`Particle type \"${name}\" already exists! Overriding`);\n  }\n  this.name = name;\n  this.minRadius = 16;\n  this.maxRadius = 16;\n  this.minDensity = 1;\n  this.maxDensity = 1;\n  this.isOutlineVisible = true;\n  this.outlineColor = \"black\";\n  this.outlineThickness = 1;\n  this.isFillVisible = true;\n  this.fillColor = \"white\";\n  this.minRestitution = 0.5;\n  this.maxRestitution = 0.5;\n  this.minFriction = 0;\n  this.maxFriction = 0;\n  this.minLinearDamping = 0;\n  this.maxLinearDamping = 0;\n  this.minAngularDamping = 0;\n  this.maxAngularDamping = 0;\n  this.collisionHandlers = Object.create(null);\n  ParticleType.types[name] = this;\n};\nParticleType.types = Object.create(null);\nParticleType.get = function(name) {\n  if (name in ParticleType.types) {\n    return ParticleType.types[name];\n  } else {\n    return null;\n  }\n}\n\nParticleType.prototype.getName = function () {\n  return this.name;\n};\nParticleType.prototype.setRadius = function (value) {\n  if (value < 0) {\n    debug.warn(`setRadius called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.minRadius = value;\n  this.maxRadius = value;\n};\nParticleType.prototype.getRadius = function () {\n  return randomRange(this.minRadius, this.maxRadius);\n};\nParticleType.prototype.setMinRadius = function (value) {\n  if (value < 0) {\n    debug.warn(`setMinRadius called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.minRadius = value;\n  if (this.maxRadius < this.minRadius) {\n    debug.info(`Setting minimum radius to ${value}, which is larger than the type's current maximum radius (${this.maxRadius})`);\n    this.maxRadius = this.minRadius;\n  }\n};\nParticleType.prototype.getMinRadius = function () {\n  return this.minRadius;\n};\nParticleType.prototype.setMaxRadius = function (value) {\n  if (value < 0) {\n    debug.warn(`setMaxRadius called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.maxRadius = value;\n  if (this.minRadius > this.maxRadius) {\n    debug.info(`Setting maximum radius to ${value}, which is smaller than the type's current minimum radius (${this.minRadius})`);\n    this.minRadius = this.maxRadius;\n  }\n};\nParticleType.prototype.getMaxRadius = function () {\n  return this.maxRadius;\n};\nParticleType.prototype.setDensity = function (value) {\n  if (value < 0) {\n    debug.warn(`setDensity called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.minDensity = value;\n  this.maxDensity = value;\n};\nParticleType.prototype.getDensity = function () {\n  return randomRange(this.minDensity, this.maxDensity);\n};\nParticleType.prototype.setMinDensity = function (value) {\n  if (value < 0) {\n    debug.warn(`setMinDensity called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  if (value === 0) {\n    debug.info('Setting minimum density to 0. Objects with density 0 behave as if they had infinite mass');\n  }\n  this.minDensity = value;\n  if (this.maxDensity < this.minDensity) {\n    debug.info(`Setting minimum density to ${value}, which is larger than the type's current maximum density (${this.maxDensity})`);\n    this.maxDensity = this.minDensity;\n  }\n};\nParticleType.prototype.getMinDensity = function () {\n  return this.minDensity;\n};\nParticleType.prototype.setMaxDensity = function (value) {\n  if (value < 0) {\n    debug.warn(`setMaxDensity called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  if (value === 0) {\n    debug.info(\"Setting maximum density to 0. All particles of this type will not be affected by collisions\");\n  }\n  this.maxDensity = value;\n  if (this.minDensity > this.maxDensity) {\n    this.minDensity = this.maxDensity;\n  }\n};\nParticleType.prototype.getMaxDensity = function () {\n  return this.maxDensity;\n};\nParticleType.prototype.setIsOutlineVisible = function (value) {\n  if (value !== true && value !== false) {\n    debug.info(`setIsOutlineVisible called with parameter value=${value}, which is not a boolean. This value is interpreted by JavaScript as a ${value ? \"truthy\" : \"falsy\"} value.`)\n  }\n  this.isOutlineVisible = value;\n};\nParticleType.prototype.getIsOutlineVisible = function () {\n  return this.isOutlineVisible;\n};\nParticleType.prototype.setOutlineColor = function (value) {\n  if (checkIfIsValidCssColor(value)) {\n    this.outlineColor = value;\n  } else {\n    debug.warn(`setOutlineColor called with parameter value=${value}, which is not a valid color! Did nothing instead`);\n  }\n};\nParticleType.prototype.getOutlineColor = function () {\n  return this.outlineColor;\n};\nParticleType.prototype.setOutlineThickness = function (value) {\n  if (value < 0) {\n    debug.warn(`setOutlineThickness called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.outlineThickness = value;\n};\nParticleType.prototype.getOutlineThickness = function () {\n  return this.outlineThickness;\n};\nParticleType.prototype.setIsFillVisible = function (value) {\n  if (value !== true && value !== false) {\n    debug.info(`setIsFillVisible called with parameter value=${value}, which is not a boolean. This value is interpreted by JavaScript as a ${value ? \"truthy\" : \"falsy\"} value.`)\n  }\n  this.isFillVisible = value;\n};\nParticleType.prototype.getIsFillVisible = function () {\n  return this.isFillVisible;\n};\nParticleType.prototype.setFillColor = function (value) {\n  if (checkIfIsValidCssColor(value)) {\n    this.fillColor = value;\n  } else {\n    debug.warn(`setFillColor called with parameter value=${value}, which is not a valid color! Did nothing instead`);\n  }\n};\nParticleType.prototype.getFillColor = function () {\n  return this.fillColor;\n};\nParticleType.prototype.setRestitution = function (value) {\n  if (value < 0) {\n    debug.warn(`setRestitution called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  if (value > 1) {\n    debug.warn(`setRestitution called with parameter value=${value}, which is greater than 1!`);\n    value = 1;\n  }\n  this.minRestitution = value;\n  this.maxRestitution = value;\n};\nParticleType.prototype.getRestitution = function () {\n  return randomRange(this.minRestitution, this.maxRestitution);\n};\nParticleType.prototype.setMinRestitution = function (value) {\n  if (value < 0) {\n    debug.warn(`setMinRestitution called with parameter value=${value}, which is less than 0!`)\n    value = 0;\n  }\n  if (value > 1) {\n    debug.warn(`setMinRestitution called with parameter value=${value}, which is greater than 1!`)\n    value = 1;\n  }\n  this.minRestitution = value;\n  if (this.maxRestitution < this.minRestitution) {\n    debug.info(`Setting minimum restitution to ${value}, which is greater than the type's current maximum restitution`)\n    this.maxRestitution = this.minRestitution;\n  }\n};\nParticleType.prototype.getMinRestitution = function () {\n  return this.minRestitution;\n};\nParticleType.prototype.setMaxRestitution = function (value) {\n  if (value < 0) {\n    debug.warn(`setMaxRestitution called with parameter value=${value}, which is less than 0!`)\n    value = 0;\n  }\n  if (value > 1) {\n    debug.warn(`setMaxRestitution called with parameter value=${value}, which is greater than 1!`)\n    value = 1;\n  }\n  this.maxRestitution = value;\n  if (this.minRestitution > this.maxRestitution) {\n    debug.info(`Setting maximum restitution to ${value}, which is less than the type's current minimum restitution`)\n    this.minRestitution = this.maxRestitution;\n  }\n};\nParticleType.prototype.getMaxRestitution = function () {\n  return this.maxRestitution;\n};\nParticleType.prototype.setFriction = function (value) {\n  if (value < 0) {\n    debug.warn(`setFriction called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  if (value > 1) {\n    debug.warn(`setFriction called with parameter value=${value}, which is greater than 1!`);\n    value = 1;\n  }\n  this.minFriction = value;\n  this.maxFriction = value;\n};\nParticleType.prototype.getFriction = function () {\n  return randomRange(this.minFriction, this.maxFriction);\n};\nParticleType.prototype.setMinFriction = function (value) {\n  if (value < 0) {\n    debug.warn(`setMinFriction called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  if (value > 1) {\n    debug.warn(`setMinFriction called with parameter value=${value}, which is greater than 1!`);\n    value = 1;\n  }\n  this.minFriction = value;\n  if (this.maxFriction < this.minFriction) {\n    debug.info(`Setting minimum friction to ${value}, which is greater than the type's current maximum friction`);\n    this.maxFriction = this.minFriction;\n  }\n};\nParticleType.prototype.getMinFriction = function () {\n  return this.minFriction;\n};\nParticleType.prototype.setMaxFriction = function (value) {\n  if (value < 0) {\n    debug.warn(`setMaxFriction called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  if (value > 1) {\n    debug.warn(`setMaxFriction called with parameter value=${value}, which is greater than 1!`);\n    value = 1;\n  }\n  this.maxFriction = value;\n  if (this.minFriction > this.maxFriction) {\n    debug.info(`Setting maximum friction to ${value}, which is less than the type's current minimum friction`);\n    this.minFriction = this.maxFriction;\n  }\n};\nParticleType.prototype.getMaxFriction = function () {\n  return this.maxFriction;\n};\nParticleType.prototype.setLinearDamping = function (value) {\n  if (value < 0) {\n    debug.warn(`setLinearDamping called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.minLinearDamping = value;\n  this.maxLinearDamping = value;\n};\nParticleType.prototype.getLinearDamping = function () {\n  return randomRange(this.minLinearDamping, this.maxLinearDamping);\n};\nParticleType.prototype.setMinLinearDamping = function (value) {\n  if (value < 0) {\n    debug.warn(`setMinLinearDamping called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.minLinearDamping = value;\n  if (this.maxLinearDamping < this.minLinearDamping) {\n    debug.info(`Setting minimum linear damping to ${value}, which is greater than the type's current maximum linear damping`);\n    this.maxLinearDamping = this.minLinearDamping\n  }\n};\nParticleType.prototype.getMinLinearDamping = function () {\n  return this.minLinearDamping;\n};\nParticleType.prototype.setMaxLinearDamping = function (value) {\n  if (value < 0) {\n    debug.warn(`setMaxLinearDamping called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.maxLinearDamping = value;\n  if (this.minLinearDamping > this.maxLinearDamping) {\n    debug.info(`Setting maximum linear damping to ${value}, which is less than the type's current minimum linear damping`);\n  }\n};\nParticleType.prototype.getMaxLinearDamping = function () {\n  return this.maxLinearDamping;\n};\nParticleType.prototype.setAngularDamping = function (value) {\n  if (value < 0) {\n    debug.warn(`setAngularDamping called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.minAngularDamping = value;\n  this.maxAngularDamping = value;\n};\nParticleType.prototype.getAngularDamping = function () {\n  return randomRange(this.minAngularDamping, this.maxAngularDamping);\n};\nParticleType.prototype.setMinAngularDamping = function (value) {\n  if (value < 0) {\n    debug.warn(`setMinAngularDamping called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.minAngularDamping = value;\n  if (this.maxAngularDamping < this.minAngularDamping) {\n    debug.info(`Setting minimum angular damping to ${value}, which is greater than the type's current maximum angular damping`);\n  }\n};\nParticleType.prototype.getMinAngularDamping = function () {\n  return this.minAngularDamping;\n};\nParticleType.prototype.setMaxAngularDamping = function (value) {\n  if (value < 0) {\n    debug.warn(`setMaxAngularDamping called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.maxAngularDamping = value;\n  if (this.minAngularDamping > this.maxAngularDamping) {\n    debug.info(`Setting maximum angular damping to ${value}, which is less than the type's current minimum angular damping`);\n  }\n};\nParticleType.prototype.getMaxAngularDamping = function () {\n  return this.maxAngularDamping;\n};\n// Return truthy values in any callback to prevent collisions from having physics-based effects\nParticleType.prototype.setCollisionHandler = function (name, callbackOrCallbackArray) {\n  let callbackArray = callbackOrCallbackArray;\n  if (typeof callbackArray === \"function\") {\n    callbackArray = [callbackArray];\n  }\n  this.collisionHandlers[name] = callbackArray;\n};\nParticleType.prototype.addCollisionHandler = function (name, callbackOrCallbackArray) {\n  let callbackArray = callbackOrCallbackArray;\n  if (!(name in this.collisionHandlers)) {\n    this.collisionHandlers[name] = [];\n  }\n  if (typeof callbackArray === \"function\") {\n    callbackArray = [callbackArray];\n  }\n  this.collisionHandlers[name].push(...callbackArray);\n};\nfunction ParticleCollection() {\n  // For now, naively just throw all elements in an array\n  this.__allElements = [];\n  this.index = 0;\n  this.gravity = new Vector(gravityX, gravityY);\n  this.wallEnergy = 0;\n  this.hasWallEnergy = false;\n  this.typeCounts = Object.create(null)\n  if (ParticleCollection.particleSystem !== null) {\n    throw \"Only one singleton ParticleCollection may exist\";\n  }\n  xList = [];\n  yList = [];\n  pixelPosList = [];\n  wList = [];\n  hList = [];\n  pixelSizeList = [];\n  shapeTypeList = [];\n  outlineColorList = [];\n  outlineThicknessList = [];\n  drawOutlineList = [];\n  fillColorList = [];\n  drawFillList = [];\n  particleCount = 0;\n}\nParticleCollection.prototype.getTypeCount = function(typeOrTypeName) {\n  let typeName;\n  if (typeof typeOrTypeName === \"string\" || typeOrTypeName instanceof String) {\n    typeName = typeOrTypeName;\n  } else if (typeOrTypeName instanceof ParticleType) {\n    typeName = typeOrTypeName.getName();\n  } else {\n    debug.error(`The parameter was not a string or ParticleType`);\n    return 0;\n  }\n  if (typeName in this.typeCounts) {\n    return this.typeCounts[typeName];\n  } else {\n    return 0;\n  }\n};\nParticleCollection.prototype.disableWallEnergy = function() {\n  this.hasWallEnergy = false;\n};\nParticleCollection.prototype.setWallEnergy = function(energy) {\n  this.wallEnergy = energy;\n  this.hasWallEnergy = true;\n};\nParticleCollection.particleSystem = null;\nParticleCollection.getCollection = function() {\n  if (ParticleCollection.particleSystem === null) {\n    ParticleCollection.particleSystem = new ParticleCollection();\n  }\n  return ParticleCollection.particleSystem;\n};\nParticleCollection.prototype.registerTypeChange = function(fromType, toType) {\n  let fromTypeName = fromType.getName();\n  let toTypeName = toType.getName();\n  if (fromTypeName in this.typeCounts) {\n    this.typeCounts[fromTypeName] -= 1;\n  } else {\n    debug.error(`Type \"${typeName}\" does not exist! Particle count display may be inaccurate`);\n  }\n  if (!(toTypeName in this.typeCounts)) {\n    this.typeCounts[toTypeName] = 0;\n  }\n  this.typeCounts[toTypeName] += 1;\n};\nParticleCollection.prototype.pruneDeletedElements = function() {\n  for (let i = 0; i < this.__allElements.length; i++) {\n    while (i < this.__allElements.length && this.__allElements[i].isRegisteredForDeletion) {\n      let replacementElement = this.__allElements.pop();\n      let typeName = this.__allElements[i].type.getName();\n      if (typeName in this.typeCounts) {\n        this.typeCounts[typeName] -= 1;\n      } else {\n        debug.error(`Type \"${typeName}\" does not exist! Particle count display may be inaccurate`);\n      }\n      if (i < this.__allElements.length) {\n        this.__allElements[i] = replacementElement;\n      }\n    }\n  }\n};\nParticleCollection.prototype.elementsTouching = function(x, y, radius, output=[]) {\n  output.length = 0; // clear array\n  // Naively\n  let idx = 0;\n  for (let i = 0; i < this.__allElements.length; i++) {\n    let c = this.__allElements[i];\n    let p = c.pos;\n    if ((p.x - x) * (p.x - x) + (p.y - y) * (p.y - y) <= (radius + c.radius) * (radius + c.radius)) {\n      output[idx] = c;\n      idx += 1\n    }\n  }\n  output.length = idx;\n  return output;\n};\nParticleCollection.prototype.isPositionFree = function(x, y, radius) {\n  // Naively\n  for (let i = 0; i < this.__allElements.length; i++) {\n    let c = this.__allElements[i];\n    let p = c.pos;\n    if ((p.x - x) * (p.x - x) + (p.y - y) * (p.y - y) <= (radius + c.radius) * (radius + c.radius)) {\n      return false;\n    }\n  }\n  return true;\n}\nParticleCollection.prototype.addParticle = function(particle) {\n  this.__allElements.push(particle);\n  particle._owner = new WeakRef(this);\n  let typeName = particle.type.getName();\n  if (!(typeName in this.typeCounts)) {\n    this.typeCounts[typeName] = 0;\n  }\n  this.typeCounts[typeName] += 1;\n  particleCount += 1;\n};\nParticleCollection.prototype.spawnFreeParticles = function(particleType, count) {\n  for (let i = 0; i < count; i++) {\n    let tries = 0;\n    let p = new Particle(particleType);\n    do {\n      p.pos.x = randomRange(-width/2 + p.radius, width/2 - p.radius);\n      p.pos.y = randomRange(-height/2 + p.radius, height/2 - p.radius);\n      tries += 1;\n    } while (tries < 100 && !this.isPositionFree(p.pos.x, p.pos.y, p.radius))\n    this.addParticle(p);\n  }\n};\nParticleCollection.prototype.getAllParticles = function() {\n  return this.__allElements;\n};\nParticleCollection.prototype.handleWallCollisions = function(particle) {\n  if (particle.pos.x < particle.radius - width/2) {\n    particle.pos.x = particle.radius - width / 2;\n    if (particle.vel.x < 0) {\n      particle.vel.x = -particle.vel.x * particle.restitution;\n      if (this.hasWallEnergy) {\n        particle.vel.x = (particle.vel.x + this.wallEnergy) / 2;\n      }\n    }\n  }\n  if (particle.pos.x > width/2 - particle.radius) {\n    particle.pos.x = width/2 - particle.radius;\n    if (particle.vel.x > 0) {\n      particle.vel.x = -particle.vel.x * particle.restitution;\n      if (this.hasWallEnergy) {\n        particle.vel.x = (particle.vel.x - this.wallEnergy) / 2;\n      }\n    }\n  }\n  if (particle.pos.y < particle.radius - height/2) {\n    particle.pos.y = particle.radius - height / 2;\n    if (particle.vel.y < 0) {\n      particle.vel.y = -particle.vel.y * particle.restitution;\n      if (this.hasWallEnergy) {\n        particle.vel.y = (particle.vel.y + this.wallEnergy) / 2;\n      }\n    }\n  }\n  if (particle.pos.y > height/2 - particle.radius) {\n    particle.pos.y = height/2 - particle.radius;\n    if (particle.vel.y > 0) {\n      particle.vel.y = -particle.vel.y * particle.restitution;\n      if (this.hasWallEnergy) {\n        particle.vel.y = (particle.vel.y - this.wallEnergy) / 2;\n      }\n    }\n  }\n}\nParticleCollection.prototype.onTimeStep = function(deltaT) {\n  this.pruneDeletedElements();\n  let particles = this.getAllParticles();\n  if (this.index >= particles.length) {\n    this.index = 0;\n  }\n  if (particles.length > 0) {\n    particles[this.index].angle %= 2 * Math.PI;\n    this.index += 1;\n  }\n\n  for (let i = 0; i < particles.length; i++) {\n    particles[i]._handleCollisions();\n  }\n  this.gravity.scale(deltaT);\n  for (let i = 0; i < particles.length; i++) {\n    particles[i].applyImpulse(this.gravity.scale(particles[i].mass));\n    this.gravity.scale(particles[i].invmass);\n    particles[i]._timeStep(deltaT);\n    this.handleWallCollisions(particles[i]);\n    particles[i].render(xList, yList, pixelPosList, wList, hList, pixelSizeList, shapeTypeList, outlineColorList, outlineThicknessList, drawOutlineList, fillColorList, drawFillList, i);\n  }\n  this.gravity.scale(1/deltaT);\n};\nParticleCollection.prototype.clear = function() {\n  this.__allElements.length = 0;\n  this.typeCounts = Object.create(null);\n};\nParticleCollection.prototype.render = function() {\n  let particles = this.getAllParticles();\n  particleCount = particles.length;\n  xList.length = particleCount;\n  yList.length = particleCount;\n  pixelPosList.length = particleCount;\n  wList.length = particleCount;\n  hList.length = particleCount;\n  pixelSizeList.length = particleCount;\n  shapeTypeList.length = particleCount;\n  outlineColorList.length = particleCount;\n  outlineThicknessList.length = particleCount;\n  drawOutlineList.length = particleCount;\n  fillColorList.length = particleCount;\n  drawFillList.length = particleCount;\n  for (let i = 0; i < particles.length; i++) {\n    particles[i].render(xList, yList, pixelPosList, wList, hList, pixelSizeList, shapeTypeList, outlineColorList, outlineThicknessList, drawOutlineList, fillColorList, drawFillList, i);\n  }\n};\nfunction Particle(type, x, y) {\n  this.isRegisteredForDeletion = false;\n  \n  this.type = null;\n  this.changeType(type);\n\n  this.pos = new Vector(x, y);\n  this.vel = new Vector();\n  this.angle = 0;\n  this.angleV = 0;\n\n  this._deltaAngleV = 0;\n  this._deltaV = new Vector();\n  this._owner = null;\n  // just to give a distinction to easily decide which particle gets to handle each pair of collisions\n  this._collisionHandlePriority = Math.random();\n};\nParticle.normal = new Vector();\nParticle.forceBuilder = new Vector();\nParticle.prototype.changeType = function(type) {\n  let fromType = this.type;\n\n  if (typeof type === \"string\" || type instanceof String) {\n    this.type = ParticleType.get(type);\n    if (this.type === null) {\n      throw TypeError(\"type did not correspond to an existing particle type\");\n    }\n  } else if (type instanceof ParticleType) {\n    this.type = type;\n  } else {\n    throw TypeError(\"type was not a ParticleType or a type name\");\n  }\n  let owner\n  if (fromType !== null && this._owner !== null && (owner = this._owner.deref()) !== undefined) {\n    owner.registerTypeChange(fromType, this.type);\n  }\n\n  this.radius = this.type.getRadius();\n  this.density = this.type.getDensity();\n  this.restitution = this.type.getRestitution();\n  this.friction = this.type.getFriction();\n  this.linearDamping = this.type.getLinearDamping();\n  this.angularDamping = this.type.getAngularDamping();\n\n  if (this.density === 0) {\n    this.mass = 0;\n    this.invmass = 0;\n    this.massMomentOfInertia = 0;\n    this.invmmi = 0;\n  } else {\n    this.mass = this.density * Math.PI * this.radius * this.radius;\n    this.invmass = 1.0 / this.mass;\n    this.massMomentOfInertia = (this.radius * this.radius * this.mass) * 0.5;\n    this.invmmi = 1/this.massMomentOfInertia;\n  }\n}; \nParticle.prototype.destroy = function () {\n  this.isRegisteredForDeletion = true;\n};\nParticle.prototype.applyImpulse = function (impulse, offset = null) {\n  // Applies an impulse at an offset relative to the center of the particle\n  this._deltaV.addScaled(impulse, this.invmass);\n  if (offset !== null) {\n    this.applyMoment(offset.crossZ(impulse));\n  }\n};\nParticle.prototype.applyMoment = function (moment) {\n  this._deltaAngleV += moment * this.invmmi;\n};\nParticle.prototype._handleCollisions = function (particleCollection=null) {\n  if (this.isRegisteredForDeletion) {\n    return;\n  }\n  if (particleCollection === null) {\n    particleCollection = this._owner.deref();\n  }\n  if (particleCollection !== undefined) {\n    let toCollideWith = particleCollection.elementsTouching(this.pos.x, this.pos.y, this.radius);\n    for (let i = 0; i < toCollideWith.length; i++) {\n      let other = toCollideWith[i];\n      // The first condition excludes collisions with self\n      if (this === other) continue;\n      // The second condition ensures that the types' collision handlers are called in a consistent order\n      if (this.type.getName() < other.type.getName()) continue;\n      // The third condition just makes sure that only one particle will handle collision in each pair\n      if (this.type.getName() === other.type.getName() && this._collisionHandlePriority < other._collisionHandlePriority) continue;\n      // The fourth option is to skip processing deleted particles\n      if (other.isRegisteredForDeletion) continue;\n      \n      let willIgnoreCollision = this._runCollisionHandlersWith(other);\n      if (!willIgnoreCollision) {\n        if (this.pos.x === other.pos.x && this.pos.y === other.pos.y) {\n          this.pos.addScaled(this.vel, -1/this.vel.mag());\n        }\n        this._resolveCollisionWith(other);\n      }\n    }\n  }\n};\nParticle.prototype._runCollisionHandlersWith = function (particle) {\n  let willIgnoreCollision = false;\n  if (particle.type.getName() in this.type.collisionHandlers) {\n    let handlers = this.type.collisionHandlers[particle.type.getName()], len = handlers.length;\n    for (let i = 0; i < len; i++) {\n      if (handlers[i].call(this, particle)) {\n        willIgnoreCollision = true;\n      }\n    }\n  }\n  if (this.type.getName() in particle.type.collisionHandlers) {\n    let handlers = particle.type.collisionHandlers[this.type.getName()], len = handlers.length;\n    for (let i = 0; i < len; i++) {\n      if (handlers[i].call(particle, this)) {\n        willIgnoreCollision = true;\n      }\n    }\n  }\n  return willIgnoreCollision;\n}\nParticle.prototype._resolveCollisionWith = function (particle) {\n  Particle.normal.set(particle.pos).sub(this.pos);\n  let penetration = this.radius + particle.radius - Particle.normal.mag();\n  Particle.normal.normalize();\n  Particle.forceBuilder.set(particle.vel).sub(this.vel); // Start with the velocity of particle relative to self\n  let velApart = Particle.normal.dot(Particle.forceBuilder);\n  Particle.forceBuilder.addScaled(Particle.normal, velApart); // It's now the tangental velocity\n\n  this._separateIfOverlapping(particle, penetration);\n  if (velApart > 0) return;\n  \n  let coefficientOfRestitution = (Math.min(this.restitution, particle.restitution)); // weird multiplication for some reason\n  let friction = Math.max(this.friction, particle.friction);\n\n  Particle.forceBuilder.scale(friction);\n  Particle.forceBuilder.addScaled(Particle.normal, coefficientOfRestitution * velApart);\n  Particle.forceBuilder.scale(Math.min(this.mass, particle.mass));\n  \n  this.applyImpulse(Particle.forceBuilder, Particle.normal.scale(this.radius));\n  Particle.normal.scale(1/this.radius);\n  Particle.forceBuilder.scale(-1);\n  particle.applyImpulse(Particle.forceBuilder, Particle.normal.scale(-particle.radius));\n};\nParticle.prototype._separateIfOverlapping = function (particle, penetration) {\n  const separateAmount = 0.9;\n  const activationAmount = 0.01;\n  \n  if (penetration > activationAmount) {\n    let massRatio = particle.mass / (this.mass + particle.mass);\n    this.pos.addScaled(Particle.normal, -penetration * separateAmount * massRatio);\n    particle.pos.addScaled(Particle.normal, penetration * separateAmount * (1 - massRatio));\n  }\n};\nParticle.prototype._timeStep = function (deltaT) {\n  this.vel.add(this._deltaV);\n  this.angleV += this._deltaAngleV;\n\n  this._deltaV.set();\n  this._deltaAngleV = 0;\n\n  this.pos.addScaled(this.vel, deltaT);\n  this.angle += this.angleV * deltaT;\n\n  this.vel.scale(Math.pow(1.0 - this.linearDamping, deltaT));\n  this.angleV *= Math.pow(1.0 - this.angularDamping, deltaT);\n};\nParticle.prototype.render = function(xList, yList, pixelPosList, wList, hList, pixelSizeList, shapeTypeList, outlineColorList, outlineThicknessList, drawOutlineList, fillColorList, drawFillList, index=-1) {\n  if (index === -1) {\n    index = xList.length;\n  }\n  xList[index] = this.pos.x;\n  yList[index] = this.pos.y;\n  vxList[index] = this.vel.x * 0.25; // lookang velocity\n  vyList[index] = this.vel.y * 0.25; // lookang velocity\n  dvxList[index] = this._deltaV.x;\n  dvyList[index] = this._deltaV.y;\n  pixelPosList[index] = true;\n  wList[index] = this.radius * 2;\n  hList[index] = this.radius * 2;\n  pixelSizeList[index] = true;\n  shapeTypeList[index] = \"ELLIPSE\";\n  outlineColorList[index] = this.type.getOutlineColor();\n  outlineThicknessList[index] = this.type.getOutlineThickness();\n  drawOutlineList[index] = this.type.getIsOutlineVisible();\n  fillColorList[index] = this.type.getFillColor();\n  drawFillList[index] = this.type.getIsFillVisible();\n  \n\n};\n"
        },
        {
          "Name": "updated PArtSim",
          "Active": "true",
          "Internal": "false",
          "Type": "LIBRARY_EDITOR",
          "Comment": "",
          "Code": "if (typeof window[\"WeakRef\"] === \"undefined\") {\n  window.WeakRef = (function (wm) {\n    function WeakRef(target) {\n      wm.set(this, target);\n    }\n    WeakRef.prototype.deref = function () {\n      return wm.get(this);\n    };\n    return WeakRef;\n  })(new WeakMap());\n}\nconst debug = Object.create(null);\ndebug.TRACE = 0;\ndebug.INFO = 1;\ndebug.WARN = 2;\ndebug.ERROR = 3;\ndebug.NONE = 4;\n\ndebug.loggingLevel = debug.TRACE;\ndebug.trace = function(msg) {\n  if (debug.loggingLevel <= debug.TRACE) {\n    console.log(msg);\n  }\n};\ndebug.info = function(msg) {\n  if (debug.loggingLevel <= debug.INFO) {\n    console.info(msg);\n  }\n};\ndebug.warn = function(msg) {\n  if (debug.loggingLevel <= debug.WARN) {\n    console.warn(msg);\n  }\n};\ndebug.error = function(msg) {\n  if (debug.loggingLevel <= debug.ERROR) {\n    console.error(msg);\n  }\n};\n\nfunction Vector(x, y) {\n  if (arguments.length === 0) {\n    x = y = 0;\n  } else if (arguments.length !== 2) {\n    debug.warn(`Vector expects 0 or 2 parameters but ${arguments.length} were given`);\n  }\n  this.x = x;\n  this.y = y;\n}\nVector.prototype.set = function(x, y) {\n  if (arguments.length === 0) {\n    this.x = this.y = 0;\n  } else if (arguments.length === 1 && \"x\" in x && \"y\" in x) {\n    this.x = x.x;\n    this.y = x.y;\n  } else if (arguments.length === 2) {\n    this.x = x;\n    this.y = y;\n  }\n  return this;\n};\nVector.prototype.clone = function() {\n  return new Vector(this.x, this.y);\n};\nVector.prototype.add = function(vector) {\n  if (!(\"x\" in vector && \"y\" in vector)) {\n    throw new TypeError(`x and y are not members of vector`);\n  }\n  this.x += vector.x;\n  this.y += vector.y;\n  return this;\n};\nVector.prototype.sub = function(vector) {\n  if (!(\"x\" in vector && \"y\" in vector)) {\n    throw new TypeError(`x and y are not members of vector`);\n  }\n  this.x -= vector.x;\n  this.y -= vector.y;\n  return this;\n};\nVector.prototype.scale = function(scalar) {\n  this.x *= scalar;\n  this.y *= scalar;\n  return this;\n};\nVector.prototype.dot = function(vector) {\n  if (!(\"x\" in vector && \"y\" in vector)) {\n    throw new TypeError(`x and y are not members of vector`);\n  }\n  return this.x * vector.x + this.y * vector.y;\n};\nVector.prototype.crossZ = function(vector) {\n  // Pretending that both vectors are 3D vectors with z=0\n  // the cross product will be a vector with x=y=0\n  // knowing the z component of this cross product can be very useful\n  if (!(\"x\" in vector && \"y\" in vector)) {\n    throw new TypeError(`x and y are not members of vector`);\n  }\n\n  return this.x * vector.y - this.y * vector.x;\n};\nVector.prototype.sqmag = function() {\n  return this.x * this.x + this.y * this.y;\n};\nVector.prototype.mag = function() {\n  return Math.hypot(this.x, this.y);\n};\nVector.prototype.normalize = function() {\n  return this.scale(1/this.mag());\n};\nVector.prototype.normalized = function(vector) {\n  if (!(arguments.length >= 1 && vector instanceof Vector)) {\n    vector = new Vector();\n  }\n  return vector.set(this).normalize();\n};\nVector.prototype.rotate = function(radians) {\n  let c = Math.cos(radians), s = Math.sin(radians);\n  let x = this.x;\n  this.x = c * this.x - s * this.y;\n  this.y = s * x + c * this.y;\n  return this;\n};\nVector.prototype.addScaled = function(vector, scalar) {\n  if (!(\"x\" in vector && \"y\" in vector)) {\n    throw new TypeError(`x and y are not members of vector`);\n  }\n  this.x += vector.x * scalar;\n  this.y += vector.y * scalar;\n};\nfunction randomRange(min, max) {\n  return min + (max - min) * Math.random();\n}\n\nfunction checkIfIsValidCssColor(color) {\n  return CSS.supports(\"color\", color);\n};\n\nfunction ParticleType(name) {\n  if (name in ParticleType.types) {\n    debug.warn(`Particle type \"${name}\" already exists! Overriding`);\n  }\n  this.name = name;\n  this.minRadius = 16;\n  this.maxRadius = 16;\n  this.minDensity = 1;\n  this.maxDensity = 1;\n  this.isOutlineVisible = true;\n  this.outlineColor = \"black\";\n  this.outlineThickness = 1;\n  this.isFillVisible = true;\n  this.fillColor = \"white\";\n  this.minRestitution = 0.5;\n  this.maxRestitution = 0.5;\n  this.minFriction = 0;\n  this.maxFriction = 0;\n  this.minLinearDamping = 0;\n  this.maxLinearDamping = 0;\n  this.minAngularDamping = 0;\n  this.maxAngularDamping = 0;\n  this.collisionHandlers = Object.create(null);\n  ParticleType.types[name] = this;\n};\nParticleType.types = Object.create(null);\nParticleType.get = function(name) {\n  if (name in ParticleType.types) {\n    return ParticleType.types[name];\n  } else {\n    return null;\n  }\n}\n\nParticleType.prototype.getName = function () {\n  return this.name;\n};\nParticleType.prototype.setRadius = function (value) {\n  if (value < 0) {\n    debug.warn(`setRadius called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.minRadius = value;\n  this.maxRadius = value;\n};\nParticleType.prototype.getRadius = function () {\n  return randomRange(this.minRadius, this.maxRadius);\n};\nParticleType.prototype.setMinRadius = function (value) {\n  if (value < 0) {\n    debug.warn(`setMinRadius called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.minRadius = value;\n  if (this.maxRadius < this.minRadius) {\n    debug.info(`Setting minimum radius to ${value}, which is larger than the type's current maximum radius (${this.maxRadius})`);\n    this.maxRadius = this.minRadius;\n  }\n};\nParticleType.prototype.getMinRadius = function () {\n  return this.minRadius;\n};\nParticleType.prototype.setMaxRadius = function (value) {\n  if (value < 0) {\n    debug.warn(`setMaxRadius called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.maxRadius = value;\n  if (this.minRadius > this.maxRadius) {\n    debug.info(`Setting maximum radius to ${value}, which is smaller than the type's current minimum radius (${this.minRadius})`);\n    this.minRadius = this.maxRadius;\n  }\n};\nParticleType.prototype.getMaxRadius = function () {\n  return this.maxRadius;\n};\nParticleType.prototype.setDensity = function (value) {\n  if (value < 0) {\n    debug.warn(`setDensity called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.minDensity = value;\n  this.maxDensity = value;\n};\nParticleType.prototype.getDensity = function () {\n  return randomRange(this.minDensity, this.maxDensity);\n};\nParticleType.prototype.setMinDensity = function (value) {\n  if (value < 0) {\n    debug.warn(`setMinDensity called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  if (value === 0) {\n    debug.info('Setting minimum density to 0. Objects with density 0 behave as if they had infinite mass');\n  }\n  this.minDensity = value;\n  if (this.maxDensity < this.minDensity) {\n    debug.info(`Setting minimum density to ${value}, which is larger than the type's current maximum density (${this.maxDensity})`);\n    this.maxDensity = this.minDensity;\n  }\n};\nParticleType.prototype.getMinDensity = function () {\n  return this.minDensity;\n};\nParticleType.prototype.setMaxDensity = function (value) {\n  if (value < 0) {\n    debug.warn(`setMaxDensity called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  if (value === 0) {\n    debug.info(\"Setting maximum density to 0. All particles of this type will not be affected by collisions\");\n  }\n  this.maxDensity = value;\n  if (this.minDensity > this.maxDensity) {\n    this.minDensity = this.maxDensity;\n  }\n};\nParticleType.prototype.getMaxDensity = function () {\n  return this.maxDensity;\n};\nParticleType.prototype.setIsOutlineVisible = function (value) {\n  if (value !== true && value !== false) {\n    debug.info(`setIsOutlineVisible called with parameter value=${value}, which is not a boolean. This value is interpreted by JavaScript as a ${value ? \"truthy\" : \"falsy\"} value.`)\n  }\n  this.isOutlineVisible = value;\n};\nParticleType.prototype.getIsOutlineVisible = function () {\n  return this.isOutlineVisible;\n};\nParticleType.prototype.setOutlineColor = function (value) {\n  if (checkIfIsValidCssColor(value)) {\n    this.outlineColor = value;\n  } else {\n    debug.warn(`setOutlineColor called with parameter value=${value}, which is not a valid color! Did nothing instead`);\n  }\n};\nParticleType.prototype.getOutlineColor = function () {\n  return this.outlineColor;\n};\nParticleType.prototype.setOutlineThickness = function (value) {\n  if (value < 0) {\n    debug.warn(`setOutlineThickness called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.outlineThickness = value;\n};\nParticleType.prototype.getOutlineThickness = function () {\n  return this.outlineThickness;\n};\nParticleType.prototype.setIsFillVisible = function (value) {\n  if (value !== true && value !== false) {\n    debug.info(`setIsFillVisible called with parameter value=${value}, which is not a boolean. This value is interpreted by JavaScript as a ${value ? \"truthy\" : \"falsy\"} value.`)\n  }\n  this.isFillVisible = value;\n};\nParticleType.prototype.getIsFillVisible = function () {\n  return this.isFillVisible;\n};\nParticleType.prototype.setFillColor = function (value) {\n  if (checkIfIsValidCssColor(value)) {\n    this.fillColor = value;\n  } else {\n    debug.warn(`setFillColor called with parameter value=${value}, which is not a valid color! Did nothing instead`);\n  }\n};\nParticleType.prototype.getFillColor = function () {\n  return this.fillColor;\n};\nParticleType.prototype.setRestitution = function (value) {\n  if (value < 0) {\n    debug.warn(`setRestitution called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  if (value > 1) {\n    debug.warn(`setRestitution called with parameter value=${value}, which is greater than 1!`);\n    value = 1;\n  }\n  this.minRestitution = value;\n  this.maxRestitution = value;\n};\nParticleType.prototype.getRestitution = function () {\n  return randomRange(this.minRestitution, this.maxRestitution);\n};\nParticleType.prototype.setMinRestitution = function (value) {\n  if (value < 0) {\n    debug.warn(`setMinRestitution called with parameter value=${value}, which is less than 0!`)\n    value = 0;\n  }\n  if (value > 1) {\n    debug.warn(`setMinRestitution called with parameter value=${value}, which is greater than 1!`)\n    value = 1;\n  }\n  this.minRestitution = value;\n  if (this.maxRestitution < this.minRestitution) {\n    debug.info(`Setting minimum restitution to ${value}, which is greater than the type's current maximum restitution`)\n    this.maxRestitution = this.minRestitution;\n  }\n};\nParticleType.prototype.getMinRestitution = function () {\n  return this.minRestitution;\n};\nParticleType.prototype.setMaxRestitution = function (value) {\n  if (value < 0) {\n    debug.warn(`setMaxRestitution called with parameter value=${value}, which is less than 0!`)\n    value = 0;\n  }\n  if (value > 1) {\n    debug.warn(`setMaxRestitution called with parameter value=${value}, which is greater than 1!`)\n    value = 1;\n  }\n  this.maxRestitution = value;\n  if (this.minRestitution > this.maxRestitution) {\n    debug.info(`Setting maximum restitution to ${value}, which is less than the type's current minimum restitution`)\n    this.minRestitution = this.maxRestitution;\n  }\n};\nParticleType.prototype.getMaxRestitution = function () {\n  return this.maxRestitution;\n};\nParticleType.prototype.setFriction = function (value) {\n  if (value < 0) {\n    debug.warn(`setFriction called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  if (value > 1) {\n    debug.warn(`setFriction called with parameter value=${value}, which is greater than 1!`);\n    value = 1;\n  }\n  this.minFriction = value;\n  this.maxFriction = value;\n};\nParticleType.prototype.getFriction = function () {\n  return randomRange(this.minFriction, this.maxFriction);\n};\nParticleType.prototype.setMinFriction = function (value) {\n  if (value < 0) {\n    debug.warn(`setMinFriction called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  if (value > 1) {\n    debug.warn(`setMinFriction called with parameter value=${value}, which is greater than 1!`);\n    value = 1;\n  }\n  this.minFriction = value;\n  if (this.maxFriction < this.minFriction) {\n    debug.info(`Setting minimum friction to ${value}, which is greater than the type's current maximum friction`);\n    this.maxFriction = this.minFriction;\n  }\n};\nParticleType.prototype.getMinFriction = function () {\n  return this.minFriction;\n};\nParticleType.prototype.setMaxFriction = function (value) {\n  if (value < 0) {\n    debug.warn(`setMaxFriction called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  if (value > 1) {\n    debug.warn(`setMaxFriction called with parameter value=${value}, which is greater than 1!`);\n    value = 1;\n  }\n  this.maxFriction = value;\n  if (this.minFriction > this.maxFriction) {\n    debug.info(`Setting maximum friction to ${value}, which is less than the type's current minimum friction`);\n    this.minFriction = this.maxFriction;\n  }\n};\nParticleType.prototype.getMaxFriction = function () {\n  return this.maxFriction;\n};\nParticleType.prototype.setLinearDamping = function (value) {\n  if (value < 0) {\n    debug.warn(`setLinearDamping called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.minLinearDamping = value;\n  this.maxLinearDamping = value;\n};\nParticleType.prototype.getLinearDamping = function () {\n  return randomRange(this.minLinearDamping, this.maxLinearDamping);\n};\nParticleType.prototype.setMinLinearDamping = function (value) {\n  if (value < 0) {\n    debug.warn(`setMinLinearDamping called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.minLinearDamping = value;\n  if (this.maxLinearDamping < this.minLinearDamping) {\n    debug.info(`Setting minimum linear damping to ${value}, which is greater than the type's current maximum linear damping`);\n    this.maxLinearDamping = this.minLinearDamping\n  }\n};\nParticleType.prototype.getMinLinearDamping = function () {\n  return this.minLinearDamping;\n};\nParticleType.prototype.setMaxLinearDamping = function (value) {\n  if (value < 0) {\n    debug.warn(`setMaxLinearDamping called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.maxLinearDamping = value;\n  if (this.minLinearDamping > this.maxLinearDamping) {\n    debug.info(`Setting maximum linear damping to ${value}, which is less than the type's current minimum linear damping`);\n  }\n};\nParticleType.prototype.getMaxLinearDamping = function () {\n  return this.maxLinearDamping;\n};\nParticleType.prototype.setAngularDamping = function (value) {\n  if (value < 0) {\n    debug.warn(`setAngularDamping called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.minAngularDamping = value;\n  this.maxAngularDamping = value;\n};\nParticleType.prototype.getAngularDamping = function () {\n  return randomRange(this.minAngularDamping, this.maxAngularDamping);\n};\nParticleType.prototype.setMinAngularDamping = function (value) {\n  if (value < 0) {\n    debug.warn(`setMinAngularDamping called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.minAngularDamping = value;\n  if (this.maxAngularDamping < this.minAngularDamping) {\n    debug.info(`Setting minimum angular damping to ${value}, which is greater than the type's current maximum angular damping`);\n  }\n};\nParticleType.prototype.getMinAngularDamping = function () {\n  return this.minAngularDamping;\n};\nParticleType.prototype.setMaxAngularDamping = function (value) {\n  if (value < 0) {\n    debug.warn(`setMaxAngularDamping called with parameter value=${value}, which is less than 0!`);\n    value = 0;\n  }\n  this.maxAngularDamping = value;\n  if (this.minAngularDamping > this.maxAngularDamping) {\n    debug.info(`Setting maximum angular damping to ${value}, which is less than the type's current minimum angular damping`);\n  }\n};\nParticleType.prototype.getMaxAngularDamping = function () {\n  return this.maxAngularDamping;\n};\n// Return truthy values in any callback to prevent collisions from having physics-based effects\nParticleType.prototype.setCollisionHandler = function (name, callbackOrCallbackArray) {\n  let callbackArray = callbackOrCallbackArray;\n  if (typeof callbackArray === \"function\") {\n    callbackArray = [callbackArray];\n  }\n  this.collisionHandlers[name] = callbackArray;\n};\nParticleType.prototype.addCollisionHandler = function (name, callbackOrCallbackArray) {\n  let callbackArray = callbackOrCallbackArray;\n  if (!(name in this.collisionHandlers)) {\n    this.collisionHandlers[name] = [];\n  }\n  if (typeof callbackArray === \"function\") {\n    callbackArray = [callbackArray];\n  }\n  this.collisionHandlers[name].push(...callbackArray);\n};\nfunction ParticleCollection() {\n  // For now, naively just throw all elements in an array\n  this.__allElements = [];\n  this.index = 0;\n  this.gravity = new Vector(gravityX, gravityY);\n  this.wallEnergy = 0;\n  this.hasWallEnergy = false;\n  this.typeCounts = Object.create(null)\n  if (ParticleCollection.particleSystem !== null) {\n    throw \"Only one singleton ParticleCollection may exist\";\n  }\n  xList = [];\n  yList = [];\n  pixelPosList = [];\n  wList = [];\n  hList = [];\n  pixelSizeList = [];\n  shapeTypeList = [];\n  outlineColorList = [];\n  outlineThicknessList = [];\n  drawOutlineList = [];\n  fillColorList = [];\n  drawFillList = [];\n  particleCount = 0;\n}\nParticleCollection.prototype.getTypeCount = function(typeOrTypeName) {\n  let typeName;\n  if (typeof typeOrTypeName === \"string\" || typeOrTypeName instanceof String) {\n    typeName = typeOrTypeName;\n  } else if (typeOrTypeName instanceof ParticleType) {\n    typeName = typeOrTypeName.getName();\n  } else {\n    debug.error(`The parameter was not a string or ParticleType`);\n    return 0;\n  }\n  if (typeName in this.typeCounts) {\n    return this.typeCounts[typeName];\n  } else {\n    return 0;\n  }\n};\nParticleCollection.prototype.disableWallEnergy = function() {\n  this.hasWallEnergy = false;\n};\nParticleCollection.prototype.setWallEnergy = function(energy) {\n  this.wallEnergy = energy;\n  this.hasWallEnergy = true;\n};\nParticleCollection.particleSystem = null;\nParticleCollection.getCollection = function() {\n  if (ParticleCollection.particleSystem === null) {\n    ParticleCollection.particleSystem = new ParticleCollection();\n  }\n  return ParticleCollection.particleSystem;\n};\nParticleCollection.prototype.registerTypeChange = function(fromType, toType) {\n  let fromTypeName = fromType.getName();\n  let toTypeName = toType.getName();\n  if (fromTypeName in this.typeCounts) {\n    this.typeCounts[fromTypeName] -= 1;\n  } else {\n    debug.error(`Type \"${typeName}\" does not exist! Particle count display may be inaccurate`);\n  }\n  if (!(toTypeName in this.typeCounts)) {\n    this.typeCounts[toTypeName] = 0;\n  }\n  this.typeCounts[toTypeName] += 1;\n};\nParticleCollection.prototype.pruneDeletedElements = function() {\n  for (let i = 0; i < this.__allElements.length; i++) {\n    while (i < this.__allElements.length && this.__allElements[i].isRegisteredForDeletion) {\n      let replacementElement = this.__allElements.pop();\n      let typeName = this.__allElements[i].type.getName();\n      if (typeName in this.typeCounts) {\n        this.typeCounts[typeName] -= 1;\n      } else {\n        debug.error(`Type \"${typeName}\" does not exist! Particle count display may be inaccurate`);\n      }\n      if (i < this.__allElements.length) {\n        this.__allElements[i] = replacementElement;\n      }\n    }\n  }\n};\nParticleCollection.prototype.elementsTouching = function(x, y, radius, output=[]) {\n  output.length = 0; // clear array\n  // Naively\n  let idx = 0;\n  for (let i = 0; i < this.__allElements.length; i++) {\n    let c = this.__allElements[i];\n    let p = c.pos;\n    if ((p.x - x) * (p.x - x) + (p.y - y) * (p.y - y) <= (radius + c.radius) * (radius + c.radius)) {\n      output[idx] = c;\n      idx += 1\n    }\n  }\n  output.length = idx;\n  return output;\n};\nParticleCollection.prototype.isPositionFree = function(x, y, radius) {\n  // Naively\n  for (let i = 0; i < this.__allElements.length; i++) {\n    let c = this.__allElements[i];\n    let p = c.pos;\n    if ((p.x - x) * (p.x - x) + (p.y - y) * (p.y - y) <= (radius + c.radius) * (radius + c.radius)) {\n      return false;\n    }\n  }\n  return true;\n}\nParticleCollection.prototype.addParticle = function(particle) {\n  this.__allElements.push(particle);\n  particle._owner = new WeakRef(this);\n  let typeName = particle.type.getName();\n  if (!(typeName in this.typeCounts)) {\n    this.typeCounts[typeName] = 0;\n  }\n  this.typeCounts[typeName] += 1;\n  particleCount += 1;\n};\nParticleCollection.prototype.spawnFreeParticles = function(particleType, count) {\n  for (let i = 0; i < count; i++) {\n    let tries = 0;\n    let p = new Particle(particleType);\n    do {\n      p.pos.x = randomRange(-width/2 + p.radius, width/2 - p.radius);\n      p.pos.y = randomRange(-height/2 + p.radius, height/2 - p.radius);\n      tries += 1;\n    } while (tries < 100 && !this.isPositionFree(p.pos.x, p.pos.y, p.radius))\n    this.addParticle(p);\n  }\n};\nParticleCollection.prototype.getAllParticles = function() {\n  return this.__allElements;\n};\nParticleCollection.prototype.handleWallCollisions = function(particle) {\n  if (particle.pos.x < particle.radius - width/2) {\n    particle.pos.x = particle.radius - width / 2;\n    if (particle.vel.x < 0) {\n      particle.vel.x = -particle.vel.x * particle.restitution;\n      if (this.hasWallEnergy) {\n        particle.vel.x = (particle.vel.x + this.wallEnergy) / 2;\n      }\n    }\n  }\n  if (particle.pos.x > width/2 - particle.radius) {\n    particle.pos.x = width/2 - particle.radius;\n    if (particle.vel.x > 0) {\n      particle.vel.x = -particle.vel.x * particle.restitution;\n      if (this.hasWallEnergy) {\n        particle.vel.x = (particle.vel.x - this.wallEnergy) / 2;\n      }\n    }\n  }\n  if (particle.pos.y < particle.radius - height/2) {\n    particle.pos.y = particle.radius - height / 2;\n    if (particle.vel.y < 0) {\n      particle.vel.y = -particle.vel.y * particle.restitution;\n      if (this.hasWallEnergy) {\n        particle.vel.y = (particle.vel.y + this.wallEnergy) / 2;\n      }\n    }\n  }\n  if (particle.pos.y > height/2 - particle.radius) {\n    particle.pos.y = height/2 - particle.radius;\n    if (particle.vel.y > 0) {\n      particle.vel.y = -particle.vel.y * particle.restitution;\n      if (this.hasWallEnergy) {\n        particle.vel.y = (particle.vel.y - this.wallEnergy) / 2;\n      }\n    }\n  }\n}\nParticleCollection.prototype.onTimeStep = function(deltaT) {\n  this.pruneDeletedElements();\n  let particles = this.getAllParticles();\n  if (this.index >= particles.length) {\n    this.index = 0;\n  }\n  if (particles.length > 0) {\n    particles[this.index].angle %= 2 * Math.PI;\n    this.index += 1;\n  }\n\n  for (let i = 0; i < particles.length; i++) {\n    particles[i]._handleCollisions();\n  }\n  this.gravity.scale(deltaT);\n  for (let i = 0; i < particles.length; i++) {\n    particles[i].applyImpulse(this.gravity.scale(particles[i].mass));\n    this.gravity.scale(particles[i].invmass);\n    particles[i]._timeStep(deltaT);\n    this.handleWallCollisions(particles[i]);\n  }\n  this.render();\n  this.gravity.scale(1/deltaT);\n};\nParticleCollection.prototype.clear = function() {\n  this.__allElements.length = 0;\n  this.typeCounts = Object.create(null);\n};\nParticleCollection.prototype.render = function() {\n  let particles = this.getAllParticles();\n  particleCount = particles.length;\n  xList.length = particleCount;\n  yList.length = particleCount;\n  pixelPosList.length = particleCount;\n  wList.length = particleCount;\n  hList.length = particleCount;\n  pixelSizeList.length = particleCount;\n  shapeTypeList.length = particleCount;\n  outlineColorList.length = particleCount;\n  outlineThicknessList.length = particleCount;\n  drawOutlineList.length = particleCount;\n  fillColorList.length = particleCount;\n  drawFillList.length = particleCount;\n  for (let i = 0; i < particles.length; i++) {\n    particles[i].render(xList, yList, pixelPosList, wList, hList, pixelSizeList, shapeTypeList, outlineColorList, outlineThicknessList, drawOutlineList, fillColorList, drawFillList, i);\n  }\n};\nfunction Particle(type, x, y) {\n  this.isRegisteredForDeletion = false;\n  \n  this.type = null;\n  this.changeType(type);\n\n  this.pos = new Vector(x, y);\n  this.vel = new Vector();\n  this.angle = 0;\n  this.angleV = 0;\n\n  this._deltaAngleV = 0;\n  this._deltaV = new Vector();\n  this._owner = null;\n  // just to give a distinction to easily decide which particle gets to handle each pair of collisions\n  this._collisionHandlePriority = Math.random();\n};\nParticle.normal = new Vector();\nParticle.forceBuilder = new Vector();\nParticle.prototype.changeType = function(type) {\n  let fromType = this.type;\n\n  if (typeof type === \"string\" || type instanceof String) {\n    this.type = ParticleType.get(type);\n    if (this.type === null) {\n      throw TypeError(\"type did not correspond to an existing particle type\");\n    }\n  } else if (type instanceof ParticleType) {\n    this.type = type;\n  } else {\n    throw TypeError(\"type was not a ParticleType or a type name\");\n  }\n  let owner\n  if (fromType !== null && this._owner !== null && (owner = this._owner.deref()) !== undefined) {\n    owner.registerTypeChange(fromType, this.type);\n  }\n\n  this.radius = this.type.getRadius();\n  this.density = this.type.getDensity();\n  this.restitution = this.type.getRestitution();\n  this.friction = this.type.getFriction();\n  this.linearDamping = this.type.getLinearDamping();\n  this.angularDamping = this.type.getAngularDamping();\n\n  if (this.density === 0) {\n    this.mass = 0;\n    this.invmass = 0;\n    this.massMomentOfInertia = 0;\n    this.invmmi = 0;\n  } else {\n    this.mass = this.density * Math.PI * this.radius * this.radius;\n    this.invmass = 1.0 / this.mass;\n    this.massMomentOfInertia = (this.radius * this.radius * this.mass) * 0.5;\n    this.invmmi = 1/this.massMomentOfInertia;\n  }\n}; \nParticle.prototype.destroy = function () {\n  this.isRegisteredForDeletion = true;\n};\nParticle.prototype.applyImpulse = function (impulse, offset = null) {\n  // Applies an impulse at an offset relative to the center of the particle\n  this._deltaV.addScaled(impulse, this.invmass);\n  if (offset !== null) {\n    this.applyMoment(offset.crossZ(impulse));\n  }\n};\nParticle.prototype.applyMoment = function (moment) {\n  this._deltaAngleV += moment * this.invmmi;\n};\nParticle.prototype._handleCollisions = function (particleCollection=null) {\n  if (this.isRegisteredForDeletion) {\n    return;\n  }\n  if (particleCollection === null) {\n    particleCollection = this._owner.deref();\n  }\n  if (particleCollection !== undefined) {\n    let toCollideWith = particleCollection.elementsTouching(this.pos.x, this.pos.y, this.radius);\n    for (let i = 0; i < toCollideWith.length; i++) {\n      let other = toCollideWith[i];\n      // The first condition excludes collisions with self\n      if (this === other) continue;\n      // The second condition ensures that the types' collision handlers are called in a consistent order\n      if (this.type.getName() < other.type.getName()) continue;\n      // The third condition just makes sure that only one particle will handle collision in each pair\n      if (this.type.getName() === other.type.getName() && this._collisionHandlePriority < other._collisionHandlePriority) continue;\n      // The fourth option is to skip processing deleted particles\n      if (other.isRegisteredForDeletion) continue;\n      \n      let willIgnoreCollision = this._runCollisionHandlersWith(other);\n      if (!willIgnoreCollision) {\n        if (this.pos.x === other.pos.x && this.pos.y === other.pos.y) {\n          this.pos.addScaled(this.vel, -1/this.vel.mag());\n        }\n        this._resolveCollisionWith(other);\n      }\n    }\n  }\n};\nParticle.prototype._runCollisionHandlersWith = function (particle) {\n  let willIgnoreCollision = false;\n  if (particle.type.getName() in this.type.collisionHandlers) {\n    let handlers = this.type.collisionHandlers[particle.type.getName()], len = handlers.length;\n    for (let i = 0; i < len; i++) {\n      if (handlers[i].call(this, particle)) {\n        willIgnoreCollision = true;\n      }\n    }\n  }\n  if (this.type.getName() in particle.type.collisionHandlers) {\n    let handlers = particle.type.collisionHandlers[this.type.getName()], len = handlers.length;\n    for (let i = 0; i < len; i++) {\n      if (handlers[i].call(particle, this)) {\n        willIgnoreCollision = true;\n      }\n    }\n  }\n  return willIgnoreCollision;\n}\nParticle.prototype._resolveCollisionWith = function (particle) {\n  Particle.normal.set(particle.pos).sub(this.pos);\n  let penetration = this.radius + particle.radius - Particle.normal.mag();\n  Particle.normal.normalize();\n  Particle.forceBuilder.set(particle.vel).sub(this.vel); // Start with the velocity of particle relative to self\n  let velApart = Particle.normal.dot(Particle.forceBuilder);\n  Particle.forceBuilder.addScaled(Particle.normal, velApart); // It's now the tangental velocity\n\n  this._separateIfOverlapping(particle, penetration);\n  if (velApart > 0) return;\n  let coefficientOfRestitution = Math.min(this.restitution, particle.restitution);\n  let friction = Math.max(this.friction, particle.friction);\n\n  Particle.forceBuilder.scale(friction);\n  Particle.forceBuilder.addScaled(Particle.normal, coefficientOfRestitution * velApart);\n  Particle.forceBuilder.scale(Math.min(this.mass, particle.mass));\n  \n  this.applyImpulse(Particle.forceBuilder, Particle.normal.scale(this.radius));\n  Particle.normal.scale(1/this.radius);\n  Particle.forceBuilder.scale(-1);\n  particle.applyImpulse(Particle.forceBuilder, Particle.normal.scale(-particle.radius));\n};\nParticle.prototype._separateIfOverlapping = function (particle, penetration) {\n  const separateAmount = 0.9;\n  const activationAmount = 0.01;\n  \n  if (penetration > activationAmount) {\n    let massRatio = particle.mass / (this.mass + particle.mass);\n    this.pos.addScaled(Particle.normal, -penetration * separateAmount * massRatio);\n    particle.pos.addScaled(Particle.normal, penetration * separateAmount * (1 - massRatio));\n  }\n};\nParticle.prototype._timeStep = function (deltaT) {\n  this.vel.add(this._deltaV);\n  this.angleV += this._deltaAngleV;\n\n  this._deltaV.set();\n  this._deltaAngleV = 0;\n\n  this.pos.addScaled(this.vel, deltaT);\n  this.angle += this.angleV * deltaT;\n\n  this.vel.scale(Math.pow(1.0 - this.linearDamping, deltaT));\n  this.angleV *= Math.pow(1.0 - this.angularDamping, deltaT);\n};\nParticle.prototype.render = function(xList, yList, pixelPosList, wList, hList, pixelSizeList, shapeTypeList, outlineColorList, outlineThicknessList, drawOutlineList, fillColorList, drawFillList, index=-1) {\n  if (index === -1) {\n    index = xList.length;\n  }\n  xList[index] = this.pos.x;\n  yList[index] = this.pos.y;\n  vxList[index] = this.vel.x * 0.25; // lookang velocity\n  vyList[index] = this.vel.y * 0.25; // lookang velocity\n  dvxList[index] = this._deltaV.x;\n  dvyList[index] = this._deltaV.y;\n  pixelPosList[index] = true;\n  wList[index] = this.radius * 2;\n  hList[index] = this.radius * 2;\n  pixelSizeList[index] = true;\n  shapeTypeList[index] = \"ELLIPSE\";\n  outlineColorList[index] = this.type.getOutlineColor();\n  outlineThicknessList[index] = this.type.getOutlineThickness();\n  drawOutlineList[index] = this.type.getIsOutlineVisible();\n  fillColorList[index] = this.type.getFillColor();\n  drawFillList[index] = this.type.getIsFillVisible();\n};"
        },
        {
          "Name": "initialize",
          "Active": "true",
          "Internal": "false",
          "Type": "LIBRARY_EDITOR",
          "Comment": "",
          "Code": "function initialize () {\n  particleSystem = ParticleCollection.getCollection();\n  particleSystem.clear();\n  init();\n  particleSystem.render();\n}\n"
        }
      ]
    },
    "elements": {
      "list": []
    }
  },
  "view": {
    "Tree": [
      {
        "Name": "controlPanel",
        "Type": "Panel",
        "Expanded": "true",
        "Properties": [
          {
            "name": "Width",
            "value": "\"100%\""
          },
          {
            "name": "Display",
            "value": "\"block\""
          }
        ],
        "Children": [
          {
            "Name": "executionPanel",
            "Type": "Panel",
            "Expanded": "true",
            "Properties": [
              {
                "name": "CSS",
                "value": "{\"flex-direction\": \"column\"}\n"
              },
              {
                "name": "Width",
                "value": "\"100%\""
              },
              {
                "name": "Display",
                "value": "\"flex\""
              }
            ],
            "Children": [
              {
                "Name": "userControlPanel",
                "Type": "Panel",
                "Expanded": "true",
                "Properties": [
                  {
                    "name": "Font",
                    "value": "fontb"
                  },
                  {
                    "name": "Display",
                    "value": "\"inline-flex\""
                  }
                ],
                "Children": [
                  {
                    "Name": "checkBox",
                    "Type": "CheckBox",
                    "Properties": [
                      {
                        "name": "Checked",
                        "value": "showtextSet"
                      },
                      {
                        "name": "Tooltip",
                        "value": "\"for very precise counting\""
                      },
                      {
                        "name": "Text",
                        "value": "\"\ud83d\udc41\""
                      },
                      {
                        "name": "Display",
                        "value": "\"none\""
                      }
                    ]
                  },
                  {
                    "Name": "checkBox2",
                    "Type": "CheckBox",
                    "Properties": [
                      {
                        "name": "Checked",
                        "value": "showvarrowSet"
                      },
                      {
                        "name": "Tooltip",
                        "value": "\"show the velocities vectors\""
                      },
                      {
                        "name": "Text",
                        "value": "\"\u2192 \""
                      },
                      {
                        "name": "Display",
                        "value": "\"none\""
                      }
                    ]
                  },
                  {
                    "Name": "temperature",
                    "Type": "ComboBox",
                    "Properties": [
                      {
                        "name": "Options",
                        "value": "temperatureNames"
                      },
                      {
                        "name": "OnFocus",
                        "value": "_pause();"
                      },
                      {
                        "name": "OnChange",
                        "value": "initialize ();\n//_play();"
                      },
                      {
                        "name": "Font",
                        "value": "fontb"
                      },
                      {
                        "name": "Width",
                        "value": "\"15%\""
                      }
                    ]
                  },
                  {
                    "Name": "blueConcentration",
                    "Type": "ComboBox",
                    "Properties": [
                      {
                        "name": "Options",
                        "value": "blueConcentrationNames"
                      },
                      {
                        "name": "OnFocus",
                        "value": "_pause();"
                      },
                      {
                        "name": "OnChange",
                        "value": "initialize ();\n//_play();"
                      },
                      {
                        "name": "Foreground",
                        "value": "\"Blue\""
                      },
                      {
                        "name": "Font",
                        "value": "fontb"
                      },
                      {
                        "name": "Width",
                        "value": "\"15%\""
                      }
                    ]
                  },
                  {
                    "Name": "redConcentration",
                    "Type": "ComboBox",
                    "Properties": [
                      {
                        "name": "Options",
                        "value": "redConcentrationNames"
                      },
                      {
                        "name": "OnFocus",
                        "value": "_pause();"
                      },
                      {
                        "name": "OnChange",
                        "value": "initialize ();\n//_play();"
                      },
                      {
                        "name": "Foreground",
                        "value": "\"Red\""
                      },
                      {
                        "name": "Font",
                        "value": "fontb"
                      },
                      {
                        "name": "Width",
                        "value": "\"15%\""
                      }
                    ]
                  },
                  {
                    "Name": "surfaceArea",
                    "Type": "ComboBox",
                    "Properties": [
                      {
                        "name": "Options",
                        "value": "surfaceAreaNames"
                      },
                      {
                        "name": "OnFocus",
                        "value": "_pause();"
                      },
                      {
                        "name": "OnChange",
                        "value": "initialize ();\n//_play();"
                      },
                      {
                        "name": "Foreground",
                        "value": "\"Red\""
                      },
                      {
                        "name": "Font",
                        "value": "fontb"
                      },
                      {
                        "name": "Width",
                        "value": "\"15%\""
                      }
                    ]
                  },
                  {
                    "Name": "timeControlPanel",
                    "Type": "Panel",
                    "Expanded": "true",
                    "Properties": [
                      {
                        "name": "Width",
                        "value": "\"40%\""
                      }
                    ],
                    "Children": [
                      {
                        "Name": "checkBox3",
                        "Type": "CheckBox",
                        "Properties": [
                          {
                            "name": "Checked",
                            "value": "fast"
                          },
                          {
                            "name": "OnCheckOff",
                            "value": "spd=1\n_update()\n"
                          },
                          {
                            "name": "Text",
                            "value": "\"checkBox3\""
                          },
                          {
                            "name": "OnCheckOn",
                            "value": "spd=20\n_update()\n"
                          },
                          {
                            "name": "Display",
                            "value": "\"none\""
                          }
                        ]
                      },
                      {
                        "Name": "playpause",
                        "Type": "TwoStateButton",
                        "Properties": [
                          {
                            "name": "OffClick",
                            "value": "_pause();\ntext = \"paused\";\n"
                          },
                          {
                            "name": "TextOn",
                            "value": "\"\u25baPlay\""
                          },
                          {
                            "name": "State",
                            "value": "_isPaused"
                          },
                          {
                            "name": "TextOff",
                            "value": "\"\u275a\u275aPause\""
                          },
                          {
                            "name": "OnClick",
                            "value": "_play();\ntext = \"playing\";\n"
                          },
                          {
                            "name": "Font",
                            "value": "fontb"
                          },
                          {
                            "name": "Width",
                            "value": "\"30%\""
                          }
                        ]
                      },
                      {
                        "Name": "Step",
                        "Type": "Button",
                        "Properties": [
                          {
                            "name": "Tooltip",
                            "value": "\"Step\""
                          },
                          {
                            "name": "Text",
                            "value": "\"\u275a\u25baStep\""
                          },
                          {
                            "name": "OnPress",
                            "value": "_step();\nparticleSystem.onTimeStep(deltaT);\n"
                          },
                          {
                            "name": "OnClick",
                            "value": "%_step%"
                          },
                          {
                            "name": "Font",
                            "value": "fontb"
                          },
                          {
                            "name": "Width",
                            "value": "\"30%\""
                          }
                        ]
                      },
                      {
                        "Name": "Reset",
                        "Type": "Button",
                        "Properties": [
                          {
                            "name": "Tooltip",
                            "value": "\"Reset\""
                          },
                          {
                            "name": "OnPress",
                            "value": "%_reset%"
                          },
                          {
                            "name": "Text",
                            "value": "\"\u21bbReset\""
                          },
                          {
                            "name": "OnClick",
                            "value": "%_reset%"
                          },
                          {
                            "name": "Font",
                            "value": "fontb"
                          },
                          {
                            "name": "Width",
                            "value": "\"30%\""
                          }
                        ]
                      },
                      {
                        "Name": "initButton",
                        "Type": "Button",
                        "Properties": [
                          {
                            "name": "Tooltip",
                            "value": "\"Initialize\""
                          },
                          {
                            "name": "ImageUrl",
                            "value": "\"/data/icons/osp/reset1.gif\""
                          },
                          {
                            "name": "OnClick",
                            "value": "%_initialize%"
                          },
                          {
                            "name": "Display",
                            "value": "\"none\""
                          }
                        ]
                      }
                    ]
                  }
                ]
              }
            ]
          }
        ]
      },
      {
        "Name": "panel",
        "Type": "Panel",
        "Expanded": "true",
        "Properties": [
          {
            "name": "CSS",
            "value": "{\"flex-flow\":\"row-reverse wrap\", \"justify-content\": \"center\"}"
          },
          {
            "name": "Display",
            "value": "\"flex\""
          }
        ],
        "Children": [
          {
            "Name": "outputPanel",
            "Type": "Panel",
            "Expanded": "true",
            "Properties": [
              {
                "name": "Font",
                "value": "fontb"
              },
              {
                "name": "Display",
                "value": "\"flex\""
              }
            ],
            "Children": [
              {
                "Name": "reactant1Panel",
                "Type": "Panel",
                "Expanded": "true",
                "Properties": [
                  {
                    "name": "CSS",
                    "value": "{\n  \"justify-content\": \"flex-end\"\n}\n"
                  },
                  {
                    "name": "Width",
                    "value": "\"18em\""
                  },
                  {
                    "name": "Display",
                    "value": "\"flex\""
                  }
                ],
                "Children": [
                  {
                    "Name": "reactant1Label",
                    "Type": "Label",
                    "Properties": [
                      {
                        "name": "CSS",
                        "value": "{\"flex-grow\":0}"
                      },
                      {
                        "name": "Text",
                        "value": "\"Reactant 1 \ud83d\udd35 = \""
                      }
                    ]
                  },
                  {
                    "Name": "reactant1Out",
                    "Type": "TextField",
                    "Properties": [
                      {
                        "name": "CSS",
                        "value": "{\"background-color\": \"white\"}"
                      },
                      {
                        "name": "Value",
                        "value": "particleSystem.getTypeCount(\"reactant1\")"
                      },
                      {
                        "name": "Editable",
                        "value": "false"
                      }
                    ]
                  }
                ]
              },
              {
                "Name": "reactant2Panel",
                "Type": "Panel",
                "Expanded": "true",
                "Properties": [
                  {
                    "name": "CSS",
                    "value": "{\n  \"justify-content\": \"flex-end\"\n}\n"
                  },
                  {
                    "name": "Width",
                    "value": "\"18em\""
                  },
                  {
                    "name": "Display",
                    "value": "\"flex\""
                  }
                ],
                "Children": [
                  {
                    "Name": "reactant2Label",
                    "Type": "Label",
                    "Properties": [
                      {
                        "name": "CSS",
                        "value": "{\"flex-grow\":0}"
                      },
                      {
                        "name": "Text",
                        "value": "\"Reactant 2 \ud83d\udd34 = \""
                      }
                    ]
                  },
                  {
                    "Name": "reactant2Out",
                    "Type": "TextField",
                    "Properties": [
                      {
                        "name": "CSS",
                        "value": "{\"background-color\": \"white\"}"
                      },
                      {
                        "name": "Background",
                        "value": "\"white\""
                      },
                      {
                        "name": "Value",
                        "value": "particleSystem.getTypeCount(\"reactant2\")"
                      },
                      {
                        "name": "Editable",
                        "value": "false"
                      }
                    ]
                  }
                ]
              },
              {
                "Name": "productPanel",
                "Type": "Panel",
                "Expanded": "true",
                "Properties": [
                  {
                    "name": "CSS",
                    "value": "{\n  \"justify-content\": \"flex-end\"\n}\n"
                  },
                  {
                    "name": "Width",
                    "value": "\"18em\""
                  },
                  {
                    "name": "Display",
                    "value": "\"flex\""
                  }
                ],
                "Children": [
                  {
                    "Name": "productLabel",
                    "Type": "Label",
                    "Properties": [
                      {
                        "name": "CSS",
                        "value": "{\"flex-grow\":0}"
                      },
                      {
                        "name": "Text",
                        "value": "\"Product \ud83d\udfe2 = \""
                      }
                    ]
                  },
                  {
                    "Name": "productOut",
                    "Type": "TextField",
                    "Properties": [
                      {
                        "name": "CSS",
                        "value": "{\"background-color\": \"white\"}"
                      },
                      {
                        "name": "Value",
                        "value": "particleSystem.getTypeCount(\"product\")"
                      },
                      {
                        "name": "Editable",
                        "value": "false"
                      }
                    ]
                  }
                ]
              },
              {
                "Name": "panel2",
                "Type": "Panel",
                "Properties": []
              }
            ]
          },
          {
            "Name": "plottingPanel",
            "Type": "PlottingPanel",
            "Expanded": "true",
            "Properties": [
              {
                "name": "Gutters",
                "value": "[0,0,0,0]"
              },
              {
                "name": "Background",
                "value": "\"#fecc99\""
              },
              {
                "name": "XFixedTick",
                "value": "0"
              },
              {
                "name": "Enabled",
                "value": "true"
              },
              {
                "name": "OnDoubleClick",
                "value": "toggleFullScreen()"
              },
              {
                "name": "YFixedTick",
                "value": "0"
              },
              {
                "name": "YAutoTicks",
                "value": "false"
              },
              {
                "name": "XTickStep",
                "value": "1"
              },
              {
                "name": "YTickStep",
                "value": "1"
              },
              {
                "name": "XAutoTicks",
                "value": "false"
              },
              {
                "name": "Height",
                "value": "height+\"px\""
              },
              {
                "name": "GraphicsMode",
                "value": "\"SVG\""
              },
              {
                "name": "Width",
                "value": "width+\"px\""
              },
              {
                "name": "BRMessage",
                "value": "\"time = \"+t.toFixed(2) "
              }
            ],
            "Children": [
              {
                "Name": "particles2",
                "Type": "ShapeSet2D",
                "Properties": [
                  {
                    "name": "FillColor",
                    "value": "fillColorList"
                  },
                  {
                    "name": "PixelPosition",
                    "value": "true"
                  },
                  {
                    "name": "ShapeType",
                    "value": "\"ELLIPSE\""
                  },
                  {
                    "name": "DrawFill",
                    "value": "drawFillList"
                  },
                  {
                    "name": "OnDrag",
                    "value": "let particles = particleSystem.getAllParticles();\nif (0 <= elementinteracted && elementinteracted < particles.length) {\n  let particle = particles[elementinteracted];\n  let point = _view.plottingPanel.getInteraction().getInteractionPoint();\n  point[0] *= width/2;\n  point[1] *= height/2;\n  if (!_isPaused) {\n    \n    particle.vel.x = point[0]; \n    particle.vel.y = point[1];\n    particle.vel.sub(particle.pos); //comment out to reduce the effects of velocity\n    //particle.vel.scale(1/deltaT); //comment out to reduce the effects of velocity\n  }\n  particle.pos.x = point[0];\n  particle.pos.y = point[1];\n  \n}\nparticleSystem.render(); // draw "
                  },
                  {
                    "name": "ElementInteracted",
                    "value": "elementinteracted"
                  },
                  {
                    "name": "NumberOfElements",
                    "value": "particleCount"
                  },
                  {
                    "name": "SizeX",
                    "value": "wList"
                  },
                  {
                    "name": "X",
                    "value": "xList"
                  },
                  {
                    "name": "LineColor",
                    "value": "outlineColorList"
                  },
                  {
                    "name": "Y",
                    "value": "yList"
                  },
                  {
                    "name": "SizeY",
                    "value": "hList"
                  },
                  {
                    "name": "PixelSize",
                    "value": "true"
                  },
                  {
                    "name": "DrawLines",
                    "value": "drawOutlineList"
                  },
                  {
                    "name": "LineWidth",
                    "value": "outlineThicknessList"
                  },
                  {
                    "name": "EnabledPosition",
                    "value": "\"ENABLED_ANY\""
                  }
                ]
              }
            ]
          }
        ]
      },
      {
        "Name": "info",
        "Type": "Panel",
        "Properties": [
          {
            "name": "Html",
            "value": "<h1>Kinetic Theory and Rates of Reaction</h1>\n<iframe \n  width=\"560\" \n  height=\"315\" \n  src=\"https://www.youtube.com/embed/ma95EpTQgW8?rel=0&amp;modestbranding=1&amp;playsinline=1\" \n  title=\"YouTube video player\" \n  frameborder=\"0\" \n  allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" \n  allowfullscreen=\"allowfullscreen\">\n</iframe>\n<a href=\"https://youtu.be/ma95EpTQgW8?si=PV0iNi2xg-3_IRF_\" target=\"_blank\">Watch on YouTube</a>\n\n    <p>This simulation illustrates the kinetic theory of particles and shows how it can be used to explain rates of reaction between two reactants.</p>\n\n    <h2>Key Variables</h2>\n    <div class=\"variable\">\n        <p><strong>Reactant 1:</strong> Represented in <span style=\"color: blue;\">blue</span>.</p>\n        <p><strong>Temperature:</strong> Affects the movement of particles.</p>\n        <p><strong>Reactant 2:</strong> Represented in <span style=\"color: red;\">red</span>, its surface area is depicted by square shape.</p>\n        <p><strong>Reactant 2 Amount:</strong> Can be present in small or large quantities.</p>\n        <p><strong>Product Formation:</strong> Represented in <span style=\"color: green;\">green</span>. The speed at which the product is formed is known as the <strong>rate of reaction</strong>.</p>\n    </div>\n\n    <h2>Need More Chemistry Interactive Simulations?</h2>\n    <ul>\n        <li><a href=\"https://iwant2study.org/lookangejss/chemistryejss/ejss_model_rate_of_catalyst_reaction/index.html\" target=\"_blank\">Rate of Catalyst Reaction Simulation</a></li>\n        <li><a href=\"https://iwant2study.org/lookangejss/chemistryejss/ejss_model_rate_of_reaction/index.html\" target=\"_blank\">Rate of Reaction Simulation</a></li>\n        <li><a href=\"https://sg.iwant2study.org/ospsg/index.php/interactive-resources/chemistry\" target=\"_blank\">More Interactive Chemistry Resources</a></li>\n    </ul>"
          }
        ]
      }
    ],
    "RootProperties": []
  },
  "metadata": {
    "APP": "WebEJS",
    "CreatedWith": "WebEJS : The web version of Easy JavaScript Simulations",
    "MoreInfo": "WebEJS 1.2",
    "version": "https://www.um.es/fem/wikis/webejs/"
  }
}