Home programs math games animations 3D art ASCII art


//I used this game engine to make, among others, my Geometry Dash Remake and my Fantasy Platformer.

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var width = canvas.width;
var height = canvas.height;
//circle: this function draws a circle.
//inputs: x position of circle's center, y position of circle's center, radius of circle, boolean fillcircle weether to fill the circle.
//outputs: none
var circle = function(x, y, radius, fillCircle) {
  ctx.beginPath();
  ctx.arc(x, y, radius, 0, Math.PI * 2, false);
  if (fillCircle) {
    ctx.fill();
  } else {
    ctx.stroke();
  }
};
//gets rid of the blurred contours and gives a sharper look without changing the resolution.
//However, it is recommended to keep the option on if you plan to have rotating objects.
var getRidOfThoseBlurryContours = function() {
  ctx.imageSmoothingEnabled = false;
}
//draws a rectangle with rounded corners
//inputs: x position of center of rectangle, y position of center of rectangle, width of rectangle, height of rectangle, rounded corner radius, color
//outputs: none
var drawRoundedSquare = function(x, y, xSize, ySize, radius, color) {
  ctx.fillStyle = color;
  xSize = xSize-2*radius;
  ySize = ySize-2*radius;
  ctx.fillRect((x-(xSize/2)), (y-(ySize/2)), xSize, ySize);
  circle((x-(xSize/2))+1, (y-(ySize/2))+1, radius, true);
  circle((x+(xSize/2))-1, (y-(ySize/2))+1, radius, true);
  circle(x-(xSize/2)+1, y+(ySize/2)-1, radius, true);
  circle(x+(xSize/2)-1, y+(ySize/2)-1, radius, true);
  ctx.fillRect((x-(xSize/2))+1, (y-(ySize/2)-radius)+1, xSize, radius);
  ctx.fillRect((x-(xSize/2)-radius)+1, (y-(ySize/2))+1, radius, ySize);
  ctx.fillRect((x+(xSize/2)+radius)-1, (y-(ySize/2))+1, -radius, ySize);
  ctx.fillRect((x+(xSize/2))-1, (y+(ySize/2)+radius)-1, -xSize, -radius);
}
//check if a rectangle is colliding with another rectangle
//inputs:

var checkSquareCollision = function(x1, y1, width1, height1, x2, y2, width2, height2) {
  if (x1+width1 > x2 && x1 < x2 + width2) {//x
    if (y1 + height1> y2 && y1 < y2 + height2) {//y
      return true;
    }  
  }  
  return false;
}
var checkPointCollision = function(px, py, x, y, width, height) {
  if (px > x && px < x + width) {//x
    if (py> y && py < y + height) {//y
      return true;
    }  
  }  
  return false;
}
var checkMultipleSquareCollisions = function(x, y, width, height, obstacles, complex=false) {
  //console.log("2");
  var occurences = [];
  for (let i = 0; i<=obstacles.length; i+=4) {
     // console.log(obstacles);
	  if (x+width > obstacles[i] && x < obstacles[i] + obstacles[i+2]) {//x
		if (y + height> obstacles[i+1] && y < obstacles[i+1] + obstacles[i+3]) {//y
		  //console.log("h");
		  if (!complex) {
		    return true;
		  } else {
		    occurences.push(i/4);
		  }
		}  
	  }  
  }
  if (complex) {
    return occurences;
  } else {
    return false;
  }
}
var checkMultiplePointCollisions = function(x, y, obstacles) {
  for (let i = 0; i<=obstacles.length; i+=4) {
	  if (x > obstacles[i] && x < obstacles[i] + obstacles[i+2]) {//x
		if (y > obstacles[i+1] && y < obstacles[i+1] + obstacles[i+3]) {//y
		  return true;
		}  
	  }  
  }
  return false;
}

var elementsEqual = function (array) {
  var equal = true;
  for (let i = 0; i < array.length; i++) {
    if (!(array[array.length - 1] == array[i])) {
      equal = false;
    }
  }
  return equal;
}



//I want a path finding function, that navigates an object around obstacles.
//	step one: build a multiple obstacle collision check function. (Done)
//	step two: build a method to glide to a target. (in progress)
var followTarget = function(x, y, tx, ty, speed, deadx = 0, deady=0) {
  //difference
  var dx = Math.abs(tx - x);
  var dy = Math.abs(ty - y);
  //has arrived?
  if(checkPointCollision(x, y, tx-deadx/2, ty-deady/2, deadx, deady)) { //dead means a dead space where the object stops.
    return [x, y];
  }
  //ratio
  if (dx != 0) {
    var ratio = dy/dx;
  } else {
    var ratio = 0;
  }
  //direction
  var sx = 1;
  if (tx - x < 0) {
    sx = -1;
  }
  var sy = 1;
  if (ty - y < 0) {
    sy = -1;
  }
  //adjust speed
  var hyp = Math.sqrt((speed*sx)**2+(ratio*speed*sy)**2);
  var aspeed = speed/hyp; //aspeed stands for actual speed
  //change of position
  var step = [(aspeed*sx)*speed, (ratio*aspeed*sy)*speed]; //x, y
  //new position
  var newPos = [x+step[0], y+step[1]];
  //return new position
  return newPos;
}
//camera function (camera has an x, y width and height; it updates all other object's xs, ys, widths and heights)
//camera follow function: an (invisible) object follows a target; camera follows object.
//...
// watch https://www.youtube.com/watch?v=eX7W-FWpx2Y to learn more about this type of camera.
var Camera = function() {
  this.x = 0;
  this.y = 0;
  this.width = width;
  this.height = height;
  this.followX = this.x;
  this.followY = this.y;
  this.zoom = 1;
  this.camerax = width/2;
  this.cameray = height/2;
  this.update = function() {};
  this.motionBlur = 0;
}
Camera.prototype.follow = function(targetx, targety, speed, x=true, y=true, deadx=0, deady=0, tDXPos= width/2, tDYPos = height/2) {
  var newPosition = followTarget(this.camerax, this.cameray, targetx, targety, speed, deadx, deady);
  if (x) {
  this.camerax = newPosition[0];
  }
  if (y) {
  this.cameray = newPosition[1];
  }
}

Camera.prototype.glideZoom = function(desiredZoom, speed) {
  var dZoom = desiredZoom - this.zoom;//zoom difference
  var change = dZoom / speed;
  this.zoom = this.zoom+change;
}
camera = new Camera();

var Obstacle = function(x, y, Owidth, Oheight, colour, name = "Obstacle", Obstacle_set = null, collisionDetection = false, camEnabled = true) {
  this.x = x;
  this.y = y;
  this.drawx = this.x;
  this.drawy = this.y;
  this.relx = width/2 - this.x;
  this.rely = height/2 - this.y;
  this.width = Owidth;
  this.height = Oheight;
  this.drawWidth = this.width * (camera.width/width);
  this.drawHeight = this.height * (camera.height/height);
  this.colour = colour;
  this.name = name;
  this.collider = [this.x, this.y, this.width, this.height];
  this.collisionDetection = collisionDetection;
  this.remove = function() {
    var index = Obstacle_set.obstacles.indexOf(this);
    var new_obstacles = Obstacle_set.obstacles;
	new_obstacles.splice(index, 1);
	Obstacle_set.obstacles = new_obstacles;
	var new_objects = GOS.gameObjects;
	index = GOS.gameObjects.indexOf(this);
    new_objects.splice(index, 1);
    console.log(new_objects);
    GOS.gameObjects = new_objects;
    Obstacle_set.colliders.splice(index*4, 4);
  }
  this.draw = function() {
    ctx.fillStyle = this.colour;
    ctx.fillRect(this.drawx, this.drawy, this.drawWidth, this.drawHeight);
  };
  this.update = function() {
    if (camEnabled) {
      this.relx = (camera.camerax - this.x)*camera.zoom;
      this.rely = (camera.cameray - this.y)*camera.zoom;
      this.drawx = width/2 - this.relx;
      this.drawy = height/2 - this.rely;
      this.drawWidth = this.width*camera.zoom;
      this.drawHeight = this.height*camera.zoom;
    } else {
      this.drawx = this.x;
      this.drawy = this.y;
      this.drawWidth = this.width;
      this.drawHeight = this.height;
    }
  }
  if (this.collisionDetection) {
    if (Obstacle_set != null ) {
      Obstacle_set.addObstacle(this);
    } else {
      console.warn("Obstacle set is null. Declaration dropped. Specific obstacle reporting this error: "+this.name+".");
    }
  }
}

var ObstacleSet = function() {
  this.colliders = [];
  this.obstacles = [];
  this.addObstacle = function(inputObstacle) {
    this.colliders.push(inputObstacle.x);
    this.colliders.push(inputObstacle.y);
    this.colliders.push(inputObstacle.width);
    this.colliders.push(inputObstacle.height);
    this.obstacles.push(inputObstacle);
    GOS.addGameObject(inputObstacle);
  }
  this.drawAllObstacles = function() {
    for (var current of this.obstacles) {
      current.draw();
    }
  }
  this.updateAllObstacles = function() {
    for (var current of this.obstacles) {
      current.update();
    }
  }
}
obset = new ObstacleSet();
var strokedRectObstacle = function(x, y, swidth, sheight, thickness, colour, name = "strokedRectObstacle", sides = [true, true, true, true], Obstacle_set = null, collisionDetection = false, cam = true) {
	//this.top = new Obstacle(x, y, swidth, thickness,  colour, name+".top", Obstacle_set, collisionDetection, cam);
	if (sides[0]) {
	this.top = new Obstacle(x, y, swidth, thickness,  colour, name+".top", Obstacle_set, collisionDetection, cam);
	}
	if (sides[1]) {
	this.right = new Obstacle(x+swidth-thickness, y, thickness, sheight,  colour, name+".right", Obstacle_set, collisionDetection, cam);
	}
	if (sides[2]) {
	this.bottom = new Obstacle(x, y+sheight-thickness, swidth, thickness,  colour, name+".bottom", Obstacle_set, collisionDetection, cam);
	}
	if (sides[3]) {
	this.left = new Obstacle(x, y, thickness, sheight,  colour, name+".left", Obstacle_set, collisionDetection, cam);
	}
	this.draw = function() {
	  if (sides[0]) {
	  this.top.draw();
	  }
	  if (sides[1]) {
	  this.right.draw();
	  }
	  if (sides[2]) {
	  this.bottom.draw();
	  }
	  if (sides[3]) {
	  this.left.draw();
	  }
	}
	this.update = function() {
	  if (sides[0]) {
	  this.top.update();
	  }
	  if (sides[1]) {
	  this.right.update();
	  }
	  if (sides[2]) {
	  this.bottom.update();
	  }
	  if (sides[3]) {
	  this.left.update();
	  }
	}
	this.remove = function() {
      this.top.remove();
      this.right.remove();
      this.bottom.remove();
      this.left.remove();
  }
}


var gameObject = function(x, y, Owidth, Oheight, colour, name = "gameObject", Obstacle_set = null, isCollider = false, camEnabled = true) {
  this.x = x;
  this.y = y;
  this.drawx = this.x;
  this.drawy = this.y;
  this.relx = width/2 - this.x;
  this.rely = height/2 - this.y;
  this.width = Owidth;
  this.height = Oheight;
  this.drawWidth = this.width * (camera.width/width);
  this.drawHeight = this.height * (camera.height/height);
  this.colour = colour;
  this.name = name;
  this.isCollider = isCollider;
  if (this.isCollider) {
    Obstacle_set.addObstacle(this);
  }
  GOS.addGameObject(this);
  this.remove = function() {
    var index = Obstacle_set.obstacles.indexOf(this);
    var new_obstacles = Obstacle_set.obstacles;
	new_obstacles.splice(index, 1);
	Obstacle_set.obstacles = new_obstacles;
    GOS.gameObjects.splice(index, 2);
    Obstacle_set.colliders.splice(index*4, 4);
  }
  this.draw = function() {
    ctx.fillStyle = this.colour;
    ctx.fillRect(this.drawx, this.drawy, this.drawWidth, this.drawHeight);
  };
  this.update = function() {
    if (camEnabled) {
      this.relx = (camera.camerax - this.x)*camera.zoom;
      this.rely = (camera.cameray - this.y)*camera.zoom;
      this.drawx = width/2 - this.relx;
      this.drawy = height/2 - this.rely;
      this.drawWidth = this.width*camera.zoom;
      this.drawHeight = this.height*camera.zoom;
    } else {
      this.drawx = this.x;
      this.drawy = this.y;
      this.drawWidth = this.width;
      this.drawHeight = this.height;
    }
    if (this.isCollider) {
      var index = Obstacle_set.obstacles.indexOf(this);
      Obstacle_set.colliders[index*4] = this.x;
      Obstacle_set.colliders[index*4+1] = this.y;
      Obstacle_set.colliders[index*4+2] = this.width;
      Obstacle_set.colliders[index*4+3] = this.height;
    }
  }
  this.checkCollision = function() {
    if (!this.isCollider) {
      return checkMultipleSquareCollisions(this.x, this.y, this.width, this.height, obset.colliders);
    } else {
      if (checkMultipleSquareCollisions(this.x, this.y, this.width, this.height, obset.colliders, true).length > 1) {
        return true;
      } else {
        return false;
      }
    }
  }
}

var rayCast = function(originx, originy, dx, dy, obstacle_set, max_runs = 10000) {
  var x = originx;
  var y = originy;
  var run = 0;
  do {
    x += dx;
    y += dy;
    run++;
    if (run > max_runs) {
      return [originx, originy];
    }
  } while (!checkMultiplePointCollisions(x, y, obstacle_set.colliders));
  return [x, y];
}
var screenToWorld = function(screenx, screeny) {
  return [camera.camerax + ((screenx - width/2)/camera.zoom), camera.cameray + ((screeny - height/2)/camera.zoom)];
}

var AllGameObjects = function() {
  this.gameObjects = [];
  this.addGameObject = function(GAME_OBJECT) {
    this.gameObjects.push(GAME_OBJECT);
  }
   this.drawAllObjects = function() {
    for (var current of this.gameObjects) {
      current.draw();
    }
    obset.drawAllObstacles();
  }
  this.updateAllObjects = function() {
    for (var current of this.gameObjects) {
      current.update();
    }
    obset.updateAllObstacles();
  }
}
GOS = new AllGameObjects(); //stands for "Game Object Set"

var degToRad = function(angle) {
  return angle * (Math.PI / 180);
}
var radToDeg = function(radians) {
  return (radians / Math.PI) * 180;
}

var rotate_start = function(pivotx, pivoty, angle) {
  var radians = angle * (Math.PI / 180);
  ctx.save();
  ctx.translate(pivotx, pivoty);
  ctx.rotate(radians);
}
var rotate_end = function() {
  ctx.restore();
}

var drawRotatedRect = function(x, y, width, height, angle, colour, filled=true) {
  rotate_start(x+width/2, y+height/2, angle);
  //rotate_start(x, y, angle);
  if (filled) {
	ctx.fillStyle = colour;
	ctx.fillRect(-width/2, -height/2, width, height);
  } else {
    ctx.strokeStyle = colour;
    ctx.strokeRect(-width/2, -height/2, width, height);
  }
  rotate_end();
}
var initialize_frame = function(border = true, mblur = 0) {
  if (mblur == 0) {
  ctx.clearRect(0, 0, width, height);
  } else {
  ctx.fillStyle = "rgba(255, 255, 255, "+mblur.toString()+")";
  ctx.fillRect(0, 0, width, height);
  }
  if (border) {
    ctx.strokeStyle = "Black";
    ctx.strokeRect(0, 0, width, height);
  }
  camera.update();
  GOS.updateAllObjects();
  GOS.drawAllObjects();
  var clicked = false;
}

var wasClicked = false;
var clickHandler = function() {
  
  var clicked = false;
  $("#canvas").click(function (event) {
    var xClick = event.offsetX;
    var yClick = event.offsetY;
    if (!wasClicked) {
      UI.checkClick(xClick, yClick);
      console.log(xClick + ", " + yClick);
    }
    clicked = true;
    wasClicked = clicked;
  });
  if (clicked) {
    wasClicked = true;
  } else {
    wasClicked = false;
  }
}
var UIComponent = function(X, Y, Owidth, Oheight, color, name="UIComponent") {
  this.x = X;
  this.y = Y;
  this.width = Owidth;
  this.height = Oheight;
  this.color = color;
  this.name = name;
  this.enabled = true;
  UI.addComponent(this);
  this.draw = function() {
    if (this.enabled) {
      ctx.fillStyle = this.color;
      ctx.fillRect(this.x, this.y, this.width, this.height);
    }
  }
  this.disable = function() {
    this.enabled = false;
  }
  this.enable = function() {
    this.enabled = true;
  }
  this.update = function() {
    //do nothing
  }
  this.onClick = function() {
    console.log(this.name + " clicked.");
  }
}

var UserInterface = function() {
  this.clickColliders = [];
  this.UIComponents = [];
  this.addComponent = function(component) {
    GOS.addGameObject(component);
    this.clickColliders.push(component.x);
    this.clickColliders.push(component.y);
    this.clickColliders.push(component.width);
    this.clickColliders.push(component.height);
    this.UIComponents.push(component);
  }
  this.checkClick = function(x, y) {
     /*if (checkMultiplePointCollisions(x, y, this.clickColliders)) {
       this.onClick();  
     }*/
     for (let i = 0; i<=this.clickColliders.length; i+=4) {
	   if (x > this.clickColliders[i] && x < this.clickColliders[i] + this.clickColliders[i+2]) {//x
		 if (y > this.clickColliders[i+1] && y < this.clickColliders[i+1] + this.clickColliders[i+3]) {//y
		   this.UIComponents[i/4].onClick();
		 }  
	   }  
  	 }
  }
}

UI = new UserInterface();

var Particle = function(initx, inity, initxvelocity, inityvelocity, forces, camEnabled) {
  this.x = initx;
  this.y = inity;
  this.xvelocity = initxvelocity;
  this.yvelocity = inityvelocity;
  this.drawx = this.x;
  this.drawy = this.y;
  this.cam = camEnabled;
  this.relx = 0;
  this.rely = 0;
  this.forces = [0, 0.0098];
  this.width = 6;
  this.height = 6;
  this.elasticity = 0.8;
  GOS.addGameObject(this);
  obset.addObstacle(this);
  this.draw = function() {
    ctx.fillStyle = "Black";
    circle(this.drawx, this.drawy, 3, true);
  }
  this.update = function() {
    if (this.cam) {
      this.relx = (camera.camerax - this.x)*camera.zoom;
      this.rely = (camera.cameray - this.y)*camera.zoom;
      this.drawx = width/2 - this.relx;
      this.drawy = height/2 - this.rely;
    } else {
      this.drawx = this.x;
      this.drawy = this.y;
    }
    this.move();
    var index = Obstacle_set.obstacles.indexOf(this);
      Obstacle_set.colliders[index*4] = this.x;
      Obstacle_set.colliders[index*4+1] = this.y;
      Obstacle_set.colliders[index*4+2] = this.width;
      Obstacle_set.colliders[index*4+3] = this.height;
  }
  this.move = function() {
    this.xvelocity += this.forces[0];
    this.yvelocity += this.forces[1];
    if (!checkMultiplePointCollisions(this.x, this.y, obset.colliders)) {
      this.x += this.xvelocity;
      this.y += this.yvelocity;
    } else {
      this.xvelocity = -this.xvelocity * this.elasticity;
      this.yvelocity = -this.yvelocity * this.elasticity;
      this.x += this.xvelocity;
      this.y += this.yvelocity;
    }
  }
}

var ParticleSystem = function(x, y, flowRate, modelParticle, camEnabled = true) {
  this.x = x;
  this.y = y;
  this.relx = 0;
  this.rely = 0;
  this.drawx = this.x;
  this.drawy = this.y;
  this.flowrate = flowRate;
  this.particle = modelParticle;
  this.time = 0;
  this.forces = [0, 0];
  this.randomness = 0.1;
  this.enabled = true;
  GOS.addGameObject(this);
  //new Particle(this.x, this.y, this.particle.xvelocity, this.particle.yvelocity, camEnabled);
  
  this.draw = function() {
    ctx.fillStyle = "Red";
    //circle(this.drawx, this.drawy, 15, true);
  }
  console.log(this.particle);
  this.update = function() {
    this.time ++;
    if (this.time == this.flowrate) {
      this.time = 0;
      if (this.enabled) {
      new Particle(this.x, this.y, this.particle.xvelocity, this.particle.yvelocity, this.forces, true);
      }
    }
      this.relx = (camera.camerax - this.x)*camera.zoom;
      this.rely = (camera.cameray - this.y)*camera.zoom;
      this.drawx = width/2 - this.relx;
      this.drawy = height/2 - this.rely;
  }
}


var drawImg = function(img, x, y, width, height) {
  //img.onload = () => {
  ctx.drawImage(img, x, y, width, height);
  //};
}

var Img = function(source) {
    this.sourceName = source;
    this.img = new Image();
    this.src = source;
    this.img.src = this.src;
    return this.img;
}

var TileMap = function(src, x, y, cutwidth, cutheight, blockwidth, blockheight, map) {
  this.src = src;
  this.cutwidth = cutwidth;
  this.cutheight = cutheight;
  this.blockw = blockwidth;
  this.blockh = blockheight;
  this.srcImg = Img(this.src);
  this.x = x;
  this.y = y;
  this.drawx = this.x;
  this.drawy = this.y;
  this.relx = 0;
  this.rely = 0;
  this.width = map[0].length * this.blockw;
  this.height = map.length * this.blockh;
  this.dbw = this.blockw;
  this.dbh = this.blockh;
  this.map = map;
  this.aWidth = 0;
  this.aHeight = 0;
  this.tiles = [];
  for (var y = 0; y this.aHeight) {
      this.aHeight = this.map.length*this.blockw;
    }
    for (var x = 0; x this.aWidth) {
        this.aWidth = this.map[y].length*this.blockh;
      }
    }
  }

  for (var y = 0; y this.frames.length-1) {
      if (this.loop) {
        this.time = 0;
      } else {
        this.pause();
      }
    }
  }
}

var AnimationManager = function() {
  this.animations = [];
  this.addAnimation = function(animation) {
    this.animations.push(animation);
  }
  this.updateAllAnimations = function() {
    for (var anim in this.animations) {
      this.animations[anim].update();
    }
  }
}
var animManager = new AnimationManager();

var sound = function(src) {
  this.sound = document.createElement("audio");
  this.sound.src = src;
  this.sound.setAttribute("preload", "auto");
  this.sound.setAttribute("controls", "none");
  this.sound.style.display = "none";
  document.body.appendChild(this.sound);
  this.play = function(){
    this.sound.play();
  }
  this.stop = function(){
    this.sound.pause();
  }
  this.play();
} 

function min(array) {return array.sort(function(a, b) {return a-b})[0]}
function max(array) {return array.sort(function(a, b) {return a-b})[array.length - 1]}

//Physics systems: https://www.youtube.com/playlist?list=PLSlpr6o9vURwq3oxVZSimY8iC-cdd3kIs (It's a series with 27 tutorials, each about 20 minutes long! I can't believe I'm watching the whole series JUST TO MAKE A RIGIDBODY SCRIPT!!!!! (I should probably delete this line…))
//Vectors


var clamp = function(value, min, max) {
  if (min == max) {
    return min;
  }
  if (min > max) {
    throw new Error("clamp Error: min is greater than max");
  }
  if (value < min) {
    return min;
  }
  if (value > max) {
    return max;
  }
  return value;	
}

var Vector = function(x, y) {
  this.x = x;
  this.y = y;
  this.add = function(vectorB) {
	return new Vector(this.x+vectorB.x, this.y+vectorB.y);
  }
  this.subtract = function(vectorB) {
	return new Vector(this.x-vectorB.x, this.y-vectorB.y);
  }
  this.scalarMultiply = function(scalar) {
    return new Vector(this.x*scalar, this.y*scalar);
  }
  this.scalarDivide = function(scalar) {
    return new Vector(this.x/scalar, this.y/scalar);
  }
  this.neg = function() {
    return new Vector(-this.x, -this.y);
  }
  this.equal = function(vectorB) {return ((this.x == vectorB.x)&&(this.y==vectorB.y))}
  this.__proto__.toString = function() {return this.x.toString() + ", " + this.y.toString();}
  
  
  this.length = function() {
    return Math.sqrt(this.x**2+this.y**2);
  }
  
  this.lengthSquared = function() {
    return this.x**2 + this.y**2;
  }
  
  
  this.distance = function(vectorB) {
    var dx = this.x - vectorB.x;
    var dy = this.y - vectorB.y;
    return Math.sqrt(dx**2+dy**2);
  }
  
  this.distanceSquared = function(vectorB) {
    var dx = this.x - vectorB.x;
    var dy = this.y - vectorB.y;
    return (dx**2+dy**2);
  }
  
  this.normalize = function() {
    var length = this.length();
    var x = this.x / length;
    var y = this.y / length;
    return new Vector(x, y);
  }
  
  this.dot = function(vectorB) {
    //a · b = ax × bx + ay × by
    return this.x * vectorB.x + this.y * vectorB.y;
  }
  
  this.cross = function(vectorB) {
    return this.x * vectorB.x  - this.y * vectorB.y;
  }
  
  this.transform = function(transform) {
    var rx = transform.cos * this.x - transform.sin * this.y;
    var ry = transform.sin * this.x + transform.cos * this.y;
    
    var tx = rx + transform.positionX;
    var ty = ry + transform.positionY;
    
    return new Vector(tx, ty);
  }
  
  
  this.visualize = function(x = 0, y = 0, color = "Black") {
    ctx.strokeStyle = color;
    ctx.beginPath();
    ctx.moveTo(x, y);
    ctx.lineTo(x + this.x, y + this.y);
    ctx.stroke();
  }
}

var Transform = function(position, angle) {
  this.positionX = position.x;
  this.positionY = position.y;
  this.sin = Math.sin(angle);
  this.cos = Math.cos(angle);
  //this.zero = new Transform(new Vector(0, 0), 0);
  
}

//Rigidbodies

var RigidBody = function(position, radius = null, width = null, height = null, color = "Black", isStatic = false, bounciness = 0, density = 1, shape = "box") {
  this.possibleShapes = ["circle", "box"];
  this.position = position;
  this.linearVelocity = new Vector(0, 0);
  this.rotation = 0;
  this.rotationalVelocity = 0;
  
  this.mass = 1;
  this.density = 1;
  this.bounciness = clamp(bounciness, 0, 1);
  this.area = 0;
  
  this.isStatic = isStatic;
  
  this.shapeType = shape;
  
  this.color = color;
  
  this.vertices = [];
  this.triangles = [];
  this.transformedVertices = [];
  
  this.transformUpdateRequired = true;
  
  
  this.radius = radius;
  if (this.shapeType == "circle")  {
    this.radius = radius;
    this.area = this.radius ** 2 * Math.PI;
    this.vertices = null;
    this.transformedVertices = null;
    this.triangles = null;
  }
  this.width = width;
  this.height = height;
  
  this.createVertices = function(width, height) {
    var left = -width / 2;
    var right = left + width;
    var bottom = height / 2;
    var top = bottom - height;
    var vertices = [];
    vertices[0] = new Vector(left, top);
    vertices[1] = new Vector(right, top);
    vertices[2] = new Vector(right, bottom);
    vertices[3] = new Vector(left, bottom);
    return vertices;
  }
  
  if (this.shapeType == "box") {
   this.area = this.width * this.height;
   /*this.vertices[0] = this.position;
   this.vertices[1] = this.position.add(new Vector(this.width, 0));
   this.vertices[2] = this.position.add(new Vector(this.width, this.height));
   this.vertices[3] = this.position.add(new Vector(0, this.height));*/
   this.vertices = this.createVertices(this.width, this.height);
   this.triangles[0] = 0;
    this.triangles[1] = 1;
    this.triangles[2] = 2;
    this.triangles[3] = 0;
    this.triangles[4] = 2;
    this.triangles[5] = 3;
    this.transformedVertices = [0, 0, 0, 0];
  }
  this.mass = this.area * this.density;
  
  this.updateParameters = function() {
    if (this.shapeType == "circle") {
      this.area = this.radius ** 2 * Math.PI;
    } else if (this.shapeType == "box") {
      this.area = this.width * this.height;
    }
  }
  
  this.getTransformedVertices = function() {
    if (this.transformUpdateRequired) {
      var mytransform = new Transform(this.position, this.rotation);
      var virtual = [0, 0, 0, 0];
      for (var i = 0; i=radii) {
      if (complex) {
        return [false, new Vector(0, 0), 0];
      } else {
        return false;
      }
    }
    var normal = bpos.subtract(apos);
    normal = normal.normalize();
    var depth = radii-distance;
    if (complex) {
      return [true, normal, depth];
    } else {
      return true;
    }
  }
  this.polygonIntersect = function(verticesa, verticesb, complex=false) {
    var depth = 100000000000;
    var normal  = new Vector(0, 0);
    for (let i = 0; i= maxb || minb >= maxa) {
        if (!complex) {
          return false;
        } else {
          return [false, new Vector(0, 0), 0];
        }
      }
      
      var axisDepth = min([maxb-mina, maxa-minb]);
      if (axisDepth < depth) {
        depth = axisDepth;
        normal = axis;
      }
      
    }
    
    for (let i = 0; i= maxb || minb >= maxa) {
        if (!complex) {
          return false;
        } else {
          return [false, new Vector(0, 0), 0];
        }
      }
      var axisDepth = min([maxb-mina, maxa-minb]);
      if (axisDepth < depth) {
        depth = axisDepth;
        normal = axis;
      }
    }
    depth /= normal.length();
    normal = normal.normalize();
    
    var centerA = this.findArithmeticMean(verticesa);
    var centerB = this.findArithmeticMean(verticesb);
    var direction = centerB.subtract(centerA);
    if (direction.dot(normal) < 0) {
      normal = normal.neg();
      //console.log("yay");
    }
    
    if (!complex) {
      return true;
    } else {
      return [true, normal, depth];
    }
  }
  this.findArithmeticMean = function(vertices) {
    var sumx = 0;
    var sumy = 0;
    for (let vert = 0; vert < vertices.length; vert++) {
      var v = new Vector(vertices[vert].x, vertices[vert].y);
      sumx += v.x;
      sumy += v.y;
    }
    return new Vector(sumx / vertices.length, sumy / vertices.length);
  }
  this.projectVertices = function(vertices, axis)  {
    var min = 100000000000;
    var max = -100000000000;
    
    for (let i = 0; i max) {
        max = proj;
      }
    }
    return [min, max]; 
  }
}
collisions = new Collisions();