html, body{
margin:0;
padding:0;
background-color:#ffffff;
}
canvas{
display:block;
position:absolute;
top:0;
left:0;
}
/**
* Raindrop fragment shader, being used by PIXI.js in the EffectCanvas object
* {{uniforms: {time: {type: string, value: number}, iResolution: {type: string, value: [*]}}, fragment: string}}
*/
const shaderData = {
uniforms: {
iResolution: {
type: 'v2',
value: [
window.innerWidth,
window.innerHeight,
],
},
vTextureSize: {
type: 'v2',
value: [
0,
0,
],
},
uTextureForeground: {
type: 'sampler2D',
value: null,
},
uTextureBackground: {
type: 'sampler2D',
value: null,
},
uTextureDropShine: {
type: 'sampler2D',
value: null,
},
},
fragment: `
precision mediump float;
//Textures
uniform sampler2D uTextureForeground;
uniform sampler2D uTextureBackground;
uniform sampler2D uTextureDropShine;
//Canvas image data
uniform sampler2D uSampler;
//The resolution and coordinates of the current pixel
uniform vec2 iResolution;
uniform vec2 vTextureSize;
varying vec2 vTextureCoord;
//Function to get the vec2 value of the current coordinate
vec2 texCoord(){
return vec2(gl_FragCoord.x, iResolution.y - gl_FragCoord.y) / iResolution;
}
//Scales the bg up and proportionally to fill the container
vec2 scaledTextureCoordinate(){
float ratioCanvas = iResolution.x / iResolution.y;
float ratioImage = vTextureSize.x / vTextureSize.y;
vec2 scale = vec2(1, 1);
vec2 offset = vec2(0, 0);
float ratioDelta = ratioCanvas - ratioImage;
if(ratioDelta >= 0.0){
scale.y = (1.0 + ratioDelta);
offset.y = ratioDelta / 2.0;
}else{
scale.x = (1.0 - ratioDelta);
offset.x = -(ratioDelta / 2.0);
}
return (texCoord() + offset) / scale;
}
//Alpha-blends two colors
vec4 blend(vec4 bg, vec4 fg){
vec3 bgm = bg.rgb * bg.a;
vec3 fgm = fg.rgb * fg.a;
float ia = 1.0 - fg.a;
float a = (fg.a + bg.a * ia);
vec3 rgb;
if(a != 0.0){
rgb = (fgm + bgm * ia) / a;
}else{
rgb = vec3(0.0,0.0,0.0);
}
return vec4(rgb,a);
}
vec2 pixel(){
return vec2(1.0, 1.0) / iResolution;
}
//Get color from fg
vec4 fgColor(){
return texture2D(uSampler, vTextureCoord);
}
void main(){
vec4 bg = texture2D(uTextureBackground, scaledTextureCoordinate());
vec4 cur = fgColor();
float d = cur.b; // "thickness"
float x = cur.g;
float y = cur.r;
float a = smoothstep(0.65, 0.7, cur.a);
vec4 smoothstepped = vec4(y, x, d, a);
vec2 refraction = (vec2(x, y) - 0.5) * 2.0;
vec2 refractionPos = scaledTextureCoordinate() + (pixel() * refraction * (256.0 + (d * 512.0)));
vec4 tex = texture2D(uTextureForeground, refractionPos);
float maxShine = 390.0;
float minShine = maxShine * 0.18;
vec2 shinePos = vec2(0.5, 0.5) + ((1.0 / 512.0) * refraction) * -(minShine + ((maxShine-minShine) * d));
vec4 shine = texture2D(uTextureDropShine, shinePos);
tex = blend(tex,shine);
vec4 fg = vec4(tex.rgb, a);
gl_FragColor = blend(bg, fg);
}
`,
};
/**
* Application Class
* Bootstraps the entire application and initializes all objects
*/
class Application {
/**
* Application constructor
*/
constructor() {
this.width = window.innerWidth;
this.height = window.innerHeight;
// Define the assets that PIXI needs to preload to use later in the application
this.loader = PIXI.loader
.add('https://stefanweck.nl/codepen/alpha.png')
.add('https://stefanweck.nl/codepen/shine.png')
.add('https://stefanweck.nl/codepen/background.jpg')
.add('https://stefanweck.nl/codepen/foreground.jpg')
.load(() => this.initialize());
}
/**
* Initialize is ran when the image loader is done loading all resources
* @return void
*/
initialize() {
// Create the Stats object and append it to the DOM
this.stats = new Stats();
this.stats.domElement.style.position = 'absolute';
this.stats.domElement.style.left = '0px';
this.stats.domElement.style.top = '0px';
this.stats.domElement.style.zIndex = '9000';
document.body.appendChild(this.stats.domElement);
// Create a new instance of the EffectCanvas which is going to produce all of the visuals
this.effectCanvas = new EffectCanvas(this.width, this.height, this.loader);
// Resize listener for the canvas to fill browser window dynamically
window.addEventListener('resize', () => this.resizeCanvas(), false);
// Start the initial loop function for the first time
this.loop();
}
/**
* Simple resize function. Reinitializing everything on the canvas while changing the width/height
* @return {void}
*/
resizeCanvas() {
this.width = window.innerWidth;
this.height = window.innerHeight;
this.effectCanvas.resize(this.width, this.height);
}
/**
* Update and render the application at least 60 times a second
* @return {void}
*/
loop() {
window.requestAnimationFrame(() => this.loop());
this.stats.begin();
this.effectCanvas.update(this.width, this.height);
this.effectCanvas.render();
this.stats.end();
}
}
/**
* EffectCanvas Class
*/
class EffectCanvas {
/**
* EffectCanvas constructor
*/
constructor(width, height, loader) {
// Create and configure the renderer
this.renderer = new PIXI.autoDetectRenderer(width, height, {
antialias: false,
transparent: false,
});
this.renderer.autoResize = true;
document.body.appendChild(this.renderer.view);
// Create a container object called the `stage`
this.stage = new PIXI.Container();
// Create a graphics object that is as big as the scene of the users window
// Else the shader won't fill the entire screen
this.background = new PIXI.Graphics();
this.background.fillAlphanumber = 0;
this.background.beginFill('0xffffff');
this.background.drawRect(0, 0, width, height);
this.background.endFill();
this.background.alpha = 0;
this.stage.addChild(this.background);
// Create the DropletManager and pass it the stage so it can insert the droplet containers into it
this.dropletManager = new DropletManager(this.stage, loader);
// Send information about the textures and the size of the background texture through the uniforms to the shader
shaderData.uniforms.uTextureDropShine.value = loader.resources['https://stefanweck.nl/codepen/shine.png'].texture;
shaderData.uniforms.uTextureBackground.value = loader.resources['https://stefanweck.nl/codepen/background.jpg'].texture;
shaderData.uniforms.uTextureForeground.value = loader.resources['https://stefanweck.nl/codepen/foreground.jpg'].texture;
shaderData.uniforms.vTextureSize.value = [
loader.resources['https://stefanweck.nl/codepen/background.jpg'].texture.width,
loader.resources['https://stefanweck.nl/codepen/background.jpg'].texture.height,
];
// Create our Pixi filter using our custom shader code
this.dropletShader = new PIXI.Filter('', shaderData.fragment, shaderData.uniforms);
// Apply it to our object
this.stage.filters = [this.dropletShader];
}
/**
* Simple resize function which redraws our graphics object that should fill the entire screen
* @return {void}
*/
resize(width, height) {
this.renderer.resize(width, height);
this.background.clear();
this.background.beginFill('0xffffff');
this.background.drawRect(0, 0, width, height);
this.background.endFill();
}
/**
* Updates the application and every child of the application
* @return {void}
*/
update(width, height) {
this.updateShader(width, height);
this.dropletManager.update(width, height);
}
/**
* Updates the uniform values in the shader
* @return {void}
*/
updateShader(width, height) {
this.dropletShader.uniforms.iResolution = [
width,
height,
];
}
/**
* Renders the application and every child of the application
* @return {void}
*/
render() {
this.renderer.render(this.stage);
}
}
/**
* DropletManager class
*/
class DropletManager {
/**
* EffectCanvas constructor
*/
constructor(stage, loader) {
let smallDropletAmount = 9000;
let largeDropletAmount = 200;
//Quick implementation to make sure there aren't out of this world thunderstorms on mobile
if(stage.width < 700){
smallDropletAmount = 3000;
largeDropletAmount = 150;
}
this.options = {
spawnRate: {
small: 0.6,
large: 0.05,
},
spawnsPerFrame: {
small: 200,
large: 5,
},
spawnMass: {
small: {
min: 1,
max: 2,
},
large: {
min: 7,
max: 10,
},
},
poolDroplets: {
small: {
min: smallDropletAmount - 500,
max: smallDropletAmount,
},
large: {
min: largeDropletAmount - 100,
max: largeDropletAmount,
},
},
maximumMassGravity: 17,
maximumMass: 21,
dropletGrowSpeed: 1,
dropletShrinkSpeed: 2,
dropletContainerSize: 100,
};
// Define a position matrix so we can calculate all the edges of a droplet in a single loop
this.positionMatrix = [
[-1, -1],
[1, -1],
[-1, 1],
[1, 1],
];
this.smallDroplets = [];
this.largeDroplets = [];
this.dropletSmallTexture = loader.resources['https://stefanweck.nl/codepen/alpha.png'].texture;
this.dropletLargeTexture = loader.resources['https://stefanweck.nl/codepen/alpha.png'].texture;
// Create a container for all the droplets
this.smallDropletContainer = new DropletPool(Droplet, this.dropletSmallTexture, this.options.poolDroplets.small.min, this.options.poolDroplets.small.max);
this.largeDropletContainer = new DropletPool(LargeDroplet, this.dropletLargeTexture, this.options.poolDroplets.large.min, this.options.poolDroplets.large.max);
stage.addChild(this.largeDropletContainer);
stage.addChild(this.smallDropletContainer);
}
/**
* Updates the application and every child of the application
* @return {void}
*/
update(width, height) {
DropletManager.removeLargeOffscreenDroplets(width, height, this.largeDroplets, this.largeDropletContainer);
// Trigger the spawn function for a small droplet as much times as is configured in the options
for (let i = 0; i < this.options.spawnsPerFrame.small; i++) {
this.spawnNewSmallDroplet(width, height);
}
// Trigger the spawn function for a large droplet as much times as is configured in the options
for (let i = 0; i < this.options.spawnsPerFrame.large; i++) {
this.spawnNewLargeDroplet(width, height);
}
// Check if we need to do anything with a large Droplet
// We don't process small droplets because they are 'dumb' objects that don't move after they've spawned
this.checkLargeDropletLogic();
}
/**
* Checks whether a big droplet hits a smaller droplet, if so, it grows by half of the smaller droplets size
* @return {void}
*/
checkLargeDropletLogic() {
// Store the length of the array so the for loop doesn't have to do that every run
const largeDropletsLength = this.largeDroplets.length;
for (let i = largeDropletsLength - 1; i >= 0; i--) {
this.updateLargeDropletSize(this.largeDroplets[i]);
this.checkDropletMovement(this.largeDroplets[i]);
this.checkLargeToSmallDropletCollision(this.largeDroplets[i]);
this.checkLargeToLargeDropletCollision(this.largeDroplets[i]);
this.removeLargeDroplets(i);
}
}
/**
* Function that checks if a single large Droplet should be removed
* @param i - The current droplet that we are processing
*/
removeLargeDroplets(i) {
if (this.largeDroplets[i].mass === 0 && this.largeDroplets[i].toBeRemoved === true) {
this.largeDropletContainer.destroy(this.largeDroplets[i]);
this.largeDroplets.splice(i, 1);
}
}
/**
* Function that updates the size of a single large Droplet
* @param droplet
*/
updateLargeDropletSize(droplet) {
// If a droplet needs to be removed, we have to shrink it down to 0
if (droplet.toBeRemoved === true) {
this.shrinkDropletSize(droplet);
} else {
this.growDropletSize(droplet);
}
// Update the width and height of the droplet based on the new mass of the droplet
droplet.width = droplet.mass * 6;
droplet.height = droplet.mass * 7;
}
/**
* Shrink a droplet based on the configured shrink speed. If it will be too small, we set the mass to 0
* @param {LargeDroplet} droplet
*/
shrinkDropletSize(droplet) {
if (droplet.mass - this.options.dropletShrinkSpeed <= 0) {
droplet.mass = 0;
} else {
droplet.mass -= this.options.dropletShrinkSpeed;
}
}
/**
* Grow a droplet based on the targetMass he has
* @param {LargeDroplet} droplet
*/
growDropletSize(droplet) {
// If a droplet has already reached its target mass, exit here
if (droplet.mass === droplet.targetMass) {
return;
}
// Check if we can grow the droplet based on the configured grow speed
if (droplet.mass + this.options.dropletGrowSpeed >= droplet.targetMass) {
droplet.mass = droplet.targetMass;
} else {
droplet.mass += this.options.dropletGrowSpeed;
}
}
/**
* Check whether a large droplet should be moving or not
* @param {LargeDroplet} droplet
* @return {void}
*/
checkDropletMovement(droplet) {
// If the droplet is going to be removed at the end of this loop, don't bother checking it
if (droplet.toBeRemoved === true) {
return;
}
// Check if the droplets mass is high enough to be moving, and if the droplet is not moving yet
if (droplet.mass < this.options.maximumMassGravity && droplet.dropletVelocity.y === 0 && droplet.dropletVelocity.x === 0) {
// There's a slight chance that the droplet starts moving
if (Math.random() < 0.01) {
droplet.dropletVelocity.y = Utils.getRandomInt(0.5, 3);
}
} else if (droplet.mass < this.options.maximumMassGravity && droplet.dropletVelocity.y !== 0) {
// There's a slight chance that the droplet shifts to the left or the right, just like real droplets attach to droplets near them
if (Math.random() < 0.1) {
droplet.x += Utils.getRandomInt(-10, 10) / 10;
}
// There's a slight chance that the droplet stops moving
if (Math.random() < 0.1) {
droplet.dropletVelocity.y = 0;
}
} else if (droplet.mass >= this.options.maximumMassGravity && droplet.dropletVelocity.y < 10) {
// The droplet is falling because it is too heavy, its speed and direction are now set
droplet.dropletVelocity.y = Utils.getRandomInt(10, 20);
droplet.dropletVelocity.x = Utils.getRandomInt(-10, 10) / 10;
}
// Increase the x and y positions of the droplet based on its velocity
droplet.y += droplet.dropletVelocity.y;
droplet.x += droplet.dropletVelocity.x;
}
/**
* Checks in which small droplet arrays the large droplet is positioned
* @param {Droplet} droplet
*/
getDropletPresenceArray(droplet) {
// Define a set of array indexes through which we hava to search for collision
const arrayIndexes = [];
const length = this.positionMatrix.length;
// Loop through each positionMatrix to calculate the position of every edge of a droplet
for (let i = 0; i < length; i++) {
const edgePosition = {
x: Math.floor((droplet.x + ((droplet.width / 7) * this.positionMatrix[i][0])) / this.options.dropletContainerSize),
y: Math.floor((droplet.y + ((droplet.height / 7) * this.positionMatrix[i][1])) / this.options.dropletContainerSize),
};
// Always push the first position in the arrayIndexes array, we use that value to compare the other edges to
if (i === 0) {
arrayIndexes.push(edgePosition);
continue;
}
// If the current position differs from the first position, store the new value because that means that this is also an array we need to check for collision
if (arrayIndexes[0].x !== edgePosition.x || arrayIndexes[0].y !== edgePosition.y) {
arrayIndexes.push(edgePosition);
}
}
return arrayIndexes;
}
/**
* Check the collision between one large Droplet and all the other Droplets
* @param droplet
*/
checkLargeToLargeDropletCollision(droplet) {
if (droplet.toBeRemoved === true) {
return;
}
// Store the length of the droplets array so we have that valua cached in the for loop
const length = this.largeDroplets.length;
for (let i = length - 1; i >= 0; i--) {
// Don't bother checking this droplet against itself
if (droplet.x === this.largeDroplets[i].x && droplet.y === this.largeDroplets[i].y) {
continue;
}
// Calculate the difference in position for the horizontal and the vertical axis
const dx = droplet.x - this.largeDroplets[i].x;
const dy = droplet.y - this.largeDroplets[i].y;
// Calculate the distance between the current droplet and the current other droplet
const distance = Math.sqrt((dx * dx) + (dy * dy));
// If the distance between the droplets is close enough, the droplet colliding increases in size
if (distance <= (droplet.width / 7) + (this.largeDroplets[i].width / 7)) {
if (droplet.mass + this.largeDroplets[i].mass <= this.options.maximumMass) {
droplet.targetMass = droplet.mass + this.largeDroplets[i].mass;
} else {
droplet.targetMass = this.options.maximumMass;
}
// The other droplet should be removed at the end of this loop
this.largeDroplets[i].toBeRemoved = true;
}
}
}
/**
* Checks whether a big droplet hits a smaller droplet, if so, it grows by half of the smaller droplets size
* @param {LargeDroplet} droplet
* @return {void}
*/
checkLargeToSmallDropletCollision(droplet) {
if (droplet.toBeRemoved === true) {
return;
}
// Define a set of array indexes through which we have to search for collision
const arrayIndexes = this.getDropletPresenceArray(droplet);
for (let i = 0; i < arrayIndexes.length; i++) {
// If the small droplet doesn't exist anymore, we can continue to the next value in the loop
if (typeof this.smallDroplets[arrayIndexes[i].x] === 'undefined' || typeof this.smallDroplets[arrayIndexes[i].x][arrayIndexes[i].y] === 'undefined') {
continue;
}
// Store the length of the array so the for loop doesn't have to do that every run
const smallDropletsLength = this.smallDroplets[arrayIndexes[i].x][arrayIndexes[i].y].length;
for (let c = smallDropletsLength - 1; c >= 0; c--) {
// Calculate the difference in position for the horizontal and the vertical axis
const dx = droplet.x - this.smallDroplets[arrayIndexes[i].x][arrayIndexes[i].y][c].x;
const dy = droplet.y - this.smallDroplets[arrayIndexes[i].x][arrayIndexes[i].y][c].y;
// Calculate the distance between the current droplet and the current other droplet
const distance = Math.sqrt((dx * dx) + (dy * dy));
// If the distance is small enough we can increase the size of the large droplet and remove the small droplet
if (distance <= (droplet.width / 7) + (this.smallDroplets[arrayIndexes[i].x][arrayIndexes[i].y][c].width / 7)) {
if (droplet.mass + (this.smallDroplets[arrayIndexes[i].x][arrayIndexes[i].y][c].mass / 3) <= this.options.maximumMass) {
droplet.targetMass = droplet.mass + (this.smallDroplets[arrayIndexes[i].x][arrayIndexes[i].y][c].mass / 3);
}
// Remove the small droplet and put it back in the object pool
this.smallDropletContainer.destroy(this.smallDroplets[arrayIndexes[i].x][arrayIndexes[i].y][c]);
this.smallDroplets[arrayIndexes[i].x][arrayIndexes[i].y].splice(c, 1);
}
}
}
}
/**
* Spawns a new small droplet on the screen based on the spawn chance
* @param {number} width
* @param {number} height
* @return {void}
*/
spawnNewSmallDroplet(width, height) {
// If our random value doesn't match the given spawn rate, we don't spawn a droplet
if (Math.random() > this.options.spawnRate.small) {
return;
}
// Get a new droplet object from the pool
const droplet = this.smallDropletContainer.get();
// If the pool decided that we can't add more droplets, exit here
if (droplet === null) {
return;
}
const position = {
x: Utils.getRandomInt(0, width),
y: Utils.getRandomInt(0, height),
};
const mass = Utils.getRandomInt(this.options.spawnMass.small.min, this.options.spawnMass.small.max);
const arrayIndex = {
x: Math.floor(position.x / this.options.dropletContainerSize),
y: Math.floor(position.y / this.options.dropletContainerSize),
};
// Make sure the droplet updates with a new position and radius
droplet.x = position.x;
droplet.y = position.y;
droplet.mass = mass;
droplet.width = droplet.mass * 8;
droplet.height = droplet.mass * 8;
if (typeof this.smallDroplets[arrayIndex.x] === 'undefined') {
this.smallDroplets[arrayIndex.x] = [];
}
if (typeof this.smallDroplets[arrayIndex.x][arrayIndex.y] === 'undefined') {
this.smallDroplets[arrayIndex.x][arrayIndex.y] = [];
}
this.smallDroplets[arrayIndex.x][arrayIndex.y].push(droplet);
}
/**
* Spawns a new large droplet on the screen based on the spawn chance
* @param {number} width
* @param {number} height
* @return {void}
*/
spawnNewLargeDroplet(width, height) {
// If our random value doesn't match the given spawn rate, we don't spawn a droplet
if (Math.random() > this.options.spawnRate.large) {
return;
}
// Get a new droplet object from the pool
const droplet = this.largeDropletContainer.get();
// If the pool decided that we can't add more droplets, exit here
if (droplet === null) {
return;
}
// Make sure the droplet updates with a new position and radius
const mass = Utils.getRandomInt(this.options.spawnMass.large.min, this.options.spawnMass.large.max);
droplet.x = Utils.getRandomInt(0, width);
droplet.y = Utils.getRandomInt(-100, height / 1.5);
droplet.mass = mass / 2;
droplet.targetMass = mass;
droplet.width = droplet.mass * 6;
droplet.height = droplet.mass * 7;
droplet.dropletVelocity.x = 0;
droplet.toBeRemoved = false;
this.largeDroplets.push(droplet);
}
/**
* Checks each droplet to see if it is positioned offscreen. If so, it's being pushed back into the object pool to be reused
* @param {number} width
* @param {number} height
* @param {Array} dropletArray
* @param {DropletPool} dropletContainer
* @return {void}
*/
static removeLargeOffscreenDroplets(width, height, dropletArray, dropletContainer) {
// Store the length of the array so the for loop doesn't have to do that every run
const length = dropletArray.length;
for (let i = length - 1; i >= 0; i--) {
if (dropletArray[i].x > width + 10 || dropletArray[i].x < -10 || dropletArray[i].y > height + 10 || dropletArray[i].y < -100) {
dropletContainer.destroy(dropletArray[i]);
dropletArray.splice(i, 1);
}
}
}
}
/**
* DropletPool class
* Functions as an object pool so we can re-use droplets over and over again
*/
class DropletPool extends PIXI.particles.ParticleContainer {
/**
* DropletPool constructor
*/
constructor(ObjectToCreate, objectTexture, startingSize, maximumSize) {
super(maximumSize, {
scale: true,
position: true,
rotation: false,
uvs: false,
alpha: false,
});
this.ObjectToCreate = ObjectToCreate;
this.objectTexture = objectTexture;
this.pool = [];
this.inUse = 0;
this.startingSize = startingSize;
this.maximumSize = maximumSize;
this.initialize();
}
/**
* Initialize the initial batch of objects that we are going to use throughout the application
* @return {void}
*/
initialize() {
for (let i = 0; i < this.startingSize; i += 1) {
const droplet = new this.ObjectToCreate(this.objectTexture);
droplet.x = -100;
droplet.y = -100;
droplet.anchor.set(0.5);
// Add the object to the PIXI Container and store it in the pool
this.addChild(droplet);
this.pool.push(droplet);
}
}
/**
* Get an object from the object pool, checks whether there is an object left or it if may create a new object otherwise
* @returns {object}
*/
get() {
// Check if we have reached the maximum number of objects, if so, return null
if (this.inUse >= this.maximumSize) {
return null;
}
// We haven't reached the maximum number of objects yet, so we are going to reuse an object
this.inUse++;
// If there are still objects in the pool return the last item from the pool
if (this.pool.length > 0) {
return this.pool.pop();
}
// The pool was empty, but we are still allowed to create a new object and return that
const droplet = new this.ObjectToCreate(this.objectTexture);
droplet.x = -100;
droplet.y = -100;
droplet.anchor.set(0.5, 0.5);
// Add the object to the PIXI Container and return it
this.addChild(droplet);
return droplet;
}
/**
* Put an element back into the object pool and reset it for later use
* @param element - The object that should be pushed back into the object pool to be reused later on
* @return {void}
*/
destroy(element) {
if (this.inUse - 1 < 0) {
console.error('Something went wrong, you cant remove more elements than there are in the total pool');
return;
}
// Move the droplet offscreen, we cant't set visible or rendering to false because that doesn't matter in a PIXI.ParticleContainer
// @see: https://github.com/pixijs/pixi.js/issues/1910
element.x = -100;
element.y = -100;
// Push the element back into the object pool so it can be reused again
this.inUse -= 1;
this.pool.push(element);
}
}
/**
* Droplet Class
*/
class Droplet extends PIXI.Sprite {
/**
* Droplet constructor
*/
constructor(texture) {
super(texture);
this.mass = 0;
}
}
/**
* LargeDroplet Class
*/
class LargeDroplet extends Droplet {
/**
* Droplet constructor
*/
constructor(texture) {
super(texture);
this.dropletVelocity = new PIXI.Point(0, 0);
this.toBeRemoved = false;
this.targetMass = 0;
}
}
/**
* Utilities Class has some functions that are needed throughout the entire application
*/
class Utils {
/**
* Returns a random integer between a given minimum and maximum value
* @param {number} min - The minimum value, can be negative
* @param {number} max - The maximum value, can be negative
* @return {number}
*/
static getRandomInt(min, max) {
return Math.floor(Math.random() * ((max - min) + 1)) + min;
}
}
/**
* Onload function is executed whenever the page is done loading, initializes the application
*/
window.onload = () => {
// Create a new instance of the application
const application = new Application();
};
View Compiled
This Pen doesn't use any external CSS resources.