Collision Detection SAT issue

I am developing a simple 2D game experiment in Javascript.

See gif (megapixel count too high for stackoverflow):
https://drive.google.com/file/d/19dCnf7jrNvqQKuo8sKiowNgbTqdOIobe/view?usp=sharing

As you can see the collision detection is working, but there are scenarios that result in collisions resolving unexpectedly. Why exactly do you think that is occurring?

Try for yourself:
WASD to move. Click to shoot

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code><!DOCTYPE html>
<html lang="en-GB">
<head>
<title>asdf</title>
</head>
<body>
<canvas id="applicationCanvas" width="1080" height="720"></canvas>
<div id="FPS"></div>
<script>
// class Network {
// connect to socket
// manage messages
// dic network objects
// interface with objects and messages
// }
// class NetworkObject {
// all variables
// generate message from variables and send as a single string
// }
class Test {
constructor (identifier)
{
let baseClass = this.constructor;
let parentClass = "unset";
while ( parentClass != undefined )
{
console.log ( baseClass.name );
if (baseClass.created == undefined)
baseClass.created = {};
baseClass.created[identifier] = this;
console.log(baseClass.created);
parentClass = Object.getPrototypeOf ( baseClass );
baseClass = parentClass;
if (parentClass == undefined)
break;
}
//console.log(Test.created);
}
}
class Test2 extends Test {
constructor (identifier)
{
super(identifier);
}
}
class Test3 extends Test2 {
constructor (identifier)
{
super(identifier);
}
}
new Test3("test");
class Vector2 {
constructor (x = 0, y = 0)
{
this.x = x;
this.y = y;
}
}
class Entity {
constructor ()
{
Entity.entities.push(this);
/* if (Entity.entities[] == undefined)
Entity.entities[Object.getPrototypeOf (this.constructor).name] = [];
if (Entity.entities[Object.getPrototypeOf (this.constructor).name][this.constructor.name] == undefined && [Object.getPrototypeOf (this.constructor).name] != this.constructor.name)
Entity.entities[Object.getPrototypeOf (this.constructor).name][this.constructor.name] = {}; */
//Entity.entities[Object.getPrototypeOf (this.constructor).name][this.constructor.name].push(this);
/* console.log(Object.getPrototypeOf (this.constructor).name);
console.log(this.constructor.name);
console.log (Entity.entities); */
}
update () {
/* on update position send request to udpate network data */
}
/* networkUpdate() {
application requests server update
object (entity) has unique id application.entities.networkdata
move local position towards network position
} */
draw () {
}
} /* Entity */
Entity.entities = [];
class Polygon extends Entity {
constructor (vertices, moveable = false) {
super();
this.position = new Vector2(0, 0);
this.vertices = vertices;
/* this.offsetVertices();
console.log("new vertices");
console.log(this.newVertices);
this.movingVertices = [];
this.movingVerticesTimer = 3; */
this.edges = [];
this.moveable = moveable;
this.velocity = { 65: { x: -25, y: 0 }, 68: { x: 25, y: 0 }, 87: { x: 0, y: -25 }, 83: { x: 0, y: 25 } }
this.buildEdges();
this.index = Polygon.polygons.push(this);
}
update () {
super.update();
//this.moveVertices();
if (this.moveable)
{
let fps = (Application.activeFrameCount / ((Application.currentFrameTimestampMS - Application.activeFrameCountTimestampMS) / 1000));
let deltaTime = fps / 1000;
if (Input.keysDown[65])
{
this.position.x += this.velocity[65].x * deltaTime;
this.position.y += this.velocity[65].y * deltaTime;
}
if (Input.keysDown[68])
{
this.position.x += this.velocity[68].x * deltaTime;
this.position.y += this.velocity[68].y * deltaTime;
}
if (Input.keysDown[87])
{
this.position.x += this.velocity[87].x * deltaTime;
this.position.y += this.velocity[87].y * deltaTime;
}
if (Input.keysDown[83])
{
this.position.x += this.velocity[83].x * deltaTime;
this.position.y += this.velocity[83].y * deltaTime;
}
}
}
draw () {
Application.canvasContext.lineWidth = 1;
if (this.colliding)
Application.canvasContext.strokeStyle = "#FF0000";
else
Application.canvasContext.strokeStyle = "#000000";
Application.canvasContext.beginPath();
Application.canvasContext.moveTo(
this.position.x + this.vertices[0].x,
this.position.y + this.vertices[0].y
);// + (Math.random() * (0.25 - 0.01) + 0.01)
for (let i = 1; i < this.vertices.length; i++) {
Application.canvasContext.lineTo(
this.position.x + this.vertices[i].x,
this.position.y + this.vertices[i].y
);
}
Application.canvasContext.closePath();
Application.canvasContext.stroke();
}
buildEdges () {
this.edges = [];
if (this.vertices.length < 3) {
console.error("Only polygons supported.");
}
for (let i = 0; i < this.vertices.length; i++) {
const a = this.vertices[i];
let b = this.vertices[0];
if (i + 1 < this.vertices.length) {
b = this.vertices[i + 1];
}
this.edges.push({
x: (b.x - a.x),
y: (b.y - a.y),
});
}
}
projectInAxis (x, y) {
let min = 10000000000;
let max = -10000000000;
for (let i = 0; i < this.vertices.length; i++) {
let px = this.position.x + this.vertices[i].x;
let py = this.position.y + this.vertices[i].y;
var projection = (px * x + py * y) / (Math.sqrt(x * x + y * y));
if (projection > max) {
max = projection;
}
if (projection < min) {
min = projection;
}
}
return { min, max };
}
testWith (otherPolygon) {
// get all edges
const edges = [];
for (let i = 0; i < this.edges.length; i++) {
edges.push(this.edges[i]);
}
for (let i = 0; i < otherPolygon.edges.length; i++) {
edges.push(otherPolygon.edges[i]);
}
let depth = Number.MAX_VALUE;
let normal = new Vector2();
// build all axis and project
for (let i = 0; i < edges.length; i++) {
// get axis
const length = Math.sqrt(edges[i].y * edges[i].y + edges[i].x * edges[i].x);
const axis = {
x: -edges[i].y / length,
y: edges[i].x / length,
};
// project polygon under axis
const { min: minA, max: maxA } = this.projectInAxis(axis.x, axis.y);
const { min: minB, max: maxB } = otherPolygon.projectInAxis(axis.x, axis.y);
if (Polygon.intervalDistance(minA, maxA, minB, maxB) > 0) {
return false;
}
let axisDepth = Math.min(maxB - minA, maxA - minB);
if (axisDepth < depth)
{
depth = axisDepth;
normal = axis;
}
}
//depth /= 2;
let magnitude = Math.sqrt((normal.x * normal.x) + (normal.y * normal.y));
normal.x /= magnitude;
normal.y /= magnitude;
this.position.x += -normal.x * depth / 2;
this.position.y += -normal.y * depth / 2;
otherPolygon.position.x += normal.x * depth / 2;
otherPolygon.position.y += normal.y * depth / 2;
let centerA = Polygon.findArithmeticMean(this.vertices);
let centerB = Polygon.findArithmeticMean(otherPolygon.vertices);
let direction = new Vector2(centerB.x - centerA.x, centerB.y - centerA.y);
// dot
if(direction.x * normal.x + direction.x * normal.x < 0)
{
normal = -normal;
}
return true;
}
onCollision(){
}
static findArithmeticMean( vertices )
{
let sumX = 0;
let sumY = 0;
for(var i = 0; i < vertices.length; i++)
{
let v = vertices[i];
sumX += v.X;
sumY += v.Y;
}
return new Vector2(sumX / vertices.length, sumY / vertices.length);
}
moveVertices() {
let fps = (Application.activeFrameCount / ((Application.currentFrameTimestampMS - Application.activeFrameCountTimestampMS) / 1000));
let deltaTime = fps / 1000;
for(var i = 0; i < this.vertices.length; i++)
{
if (this.movingVertices[i] == undefined) {
this.movingVertices[i] = new Vector2();
this.movingVertices[i].x = this.vertices[i].x;
this.movingVertices[i].y = this.vertices[i].y;
}
this.movingVertices[i].x += (this.newVertices[i].x - this.movingVertices[i].x) * 0.1 * deltaTime;
this.movingVertices[i].y += (this.newVertices[i].y - this.movingVertices[i].y) * 0.1 * deltaTime;
// }
/* if (this.movingVertices[i] == undefined)
{
this.movingVertices[i] = new Vector2();
this.movingVertices[i].x = this.newVertices[i].x;
this.movingVertices[i].y = this.newVertices[i].y;
} */
}
/* for(var i = 0; i < this.vertices.length; i++) {
if (this.movingVertices[i] == undefined)
{ */
/* this.movingVertices[i] = new Vector2();
this.movingVertices[i].x = this.newVertices[i].x;
this.movingVertices[i].y = this.newVertices[i].y;
}
/* this.movingVertices[i].x += (this.newVertices[i].x - this.movingVertices[i].x) * 0.001 * deltaTime;
this.movingVertices[i].y += (this.newVertices[i].y - this.movingVertices[i].y) * 0.001 * deltaTime;
/* if (Math.abs(this.movingVertices[i].x - this.newVertices[i].x) < 1 && Math.abs(this.movingVertices[i].y - this.newVertices[i].y) < 1)
{
this.offsetVertices(i);
}
}*/
/* this.movingVerticesTimer -= deltaTime;
if (this.movingVerticesTimer <= 0) {
this.movingVerticesTimer = 10;
this.offsetVertices();
this.movingVertices = this.newVertices;
} */
}
offsetVertices(index = undefined) {
let singleIndex = true;
if (index == undefined)
{
index = 0
singleIndex = false;
}
this.newVertices = [];
for(; index < this.vertices.length; index++) {
this.newVertices[index] = new Vector2();
this.newVertices[index].x = this.vertices[index].x + (Math.random() * ((50 - 1) + 1));
this.newVertices[index].y = this.vertices[index].y + (Math.random() * ((50 - 1) + 1));
if (singleIndex)
break;
}
console.log("Set new vertices offset from original positions");
console.log(this.newVertices);
}
static intervalDistance(minA, maxA, minB, maxB) {
if (minA < minB) {
return (minB - maxA);
}
return (minA - maxB);
}
static testCollisions () {
for (let i = 0; i < Polygon.polygons.length; i++) {
for (let ii = 0; ii < Polygon.polygons.length; ii++) {
if (i == ii)
continue;
if (Polygon.polygons[i].testWith(Polygon.polygons[ii])) {
//console.log("Tested with index: ", i);
Polygon.polygons[i].colliding = true;
Polygon.polygons[ii].colliding = true;
Polygon.polygons[i].onCollision();
Polygon.polygons[ii].onCollision();
// Add collisions array. If removing this collision results in 0 count then toggle colliding bool
}
else {
Polygon.polygons[i].colliding = false;
Polygon.polygons[ii].colliding = false;
}
}
}
}
}
Polygon.polygons = [];
class Shot extends Polygon {
constructor (vertices) {
super(vertices);
//this.position = { x: 10, y: 10 };
//this.direction = {};
}
update() {
//let fps = (Application.activeFrameCount / ((Application.currentFrameTimestampMS - Application.activeFrameCountTimestampMS) / 1000));
let deltaTime = Application.progressMS / 1000;
this.position.x += this.direction.x * 0.25 * deltaTime;
this.position.y += this.direction.y * 0.25 * deltaTime;
}
draw() {
super.draw();
/* Application.canvasContext.lineWidth = 5;
Application.canvasContext.strokeStyle = "#000000";
Application.canvasContext.beginPath();
Application.canvasContext.moveTo(this.position.x, this.position.y);
Application.canvasContext.lineTo(this.position.x + 5, this.position.y + 5);
Application.canvasContext.closePath();
Application.canvasContext.stroke(); */
}
onCollision() {
super.onCollision();
}
}
/* class Event {
} */
class Input {
constructor () {
}
}
Input.keysDown = {};
document.addEventListener("keydown", function (event) {
Input.keysDown[event.which] = true;
console.log (Input.keysDown);
});
document.addEventListener("keyup", function (event) {
delete Input.keysDown[event.which];
// console.log (Input.keysDown);
});
class Application
{
static start () {
//Application.lineObject = new LineObject();
Application.player = new Polygon ([
{ x: 0, y: 0 },
{ x: 50, y: 0 },
{ x: 50, y: 50 },
{ x: 0, y: 50 },
], true);
new Polygon ([
{ x: 0, y: 0 },
{ x: 50, y: 0 },
{ x: 50, y: 50 },
{ x: 0, y: 50 },
]);
new Polygon ([
{ x: 0, y: 0 },
{ x: 50, y: 0 },
{ x: 50, y: 50 },
{ x: 0, y: 50 },
]);
new Polygon ([
{ x: 12.5, y: 0 },
{ x: 62.5, y: 100 },
{ x: 0, y: 100 },
]);
Application.nextScreenFrame();
}
static nextScreenFrame () {
requestAnimationFrame (Application.screenFrame);
}
static screenFrame (frameTimestamp)
{
Application.currentFrameTimestampMS = new Date().getTime();
Application.progressMS = Application.currentFrameTimestampMS - Application.lastFrameTimestampMS;
if (Application.progressMS >= Application.frameIntervalMS * Application.progressVariance)
{
Application.updateFrame();
Application.drawFrame();
Application.progressPerSecondMS += Application.progressMS;
Application.progressVariance = 1 + (1 - (Application.progressMS / (Application.progressPerSecondMS / Application.activeFrameCount)) * 1.8);
// In the event of a decrease in average progress, progress will already be less than the frame interval. No need to increase the frame interval. This does not really matter in execution, but is good practice.
if (Application.progressVariance > 1)
Application.progressVariance = 1;
Application.lastFrameTimestampMS = Application.currentFrameTimestampMS;
Application.activeFrameCount++;
if (Application.activeFrameCount == Application.fps * 5)
{
Application.activeFrameCountTimestampMS = Application.currentFrameTimestampMS;
Application.activeFrameCount = 1;
Application.maxFrameCount = 1;
Application.progressPerSecondMS = Application.progressMS;
}
if (Application.activeFrameCount > Application.fps)
{
Application.FPS.innerHTML = "FPS: " + (Application.activeFrameCount / ((Application.currentFrameTimestampMS - Application.activeFrameCountTimestampMS) / 1000)).toFixed (2) + "<br>Hertz: " + (Application.maxFrameCount / ((Application.currentFrameTimestampMS - Application.activeFrameCountTimestampMS) / 1000)).toFixed (2);
}
else
{
Application.FPS.innerHTML = "FPS: ...<br>Hertz: ...";
}
/* if (Math.floor ((timestamp - startTime) / 1000) % 6 == 0)
$(".log").html((actualFramesPerSecond / ((progressPerSecond) / 1000)).toFixed(1) + " / " + (possibleFramesPerSecond / ((progressPerSecond) / 1000)).toFixed(1));
if ((fillType == '-' && newFillPerc == 100) ||
(fillType == '+' && newFillPerc == 0))
{
$(".log").html(timestamp - meterEmptyStartTimestamp);
meterEmptyStartTimestamp = timestamp;
} */
}
Application.maxFrameCount++;
Application.nextScreenFrame();
}
static updateFrame ()
{
for (var i = 0; i < Entity.entities.length; i++)
Entity.entities[i].update();
Polygon.testCollisions();
}
static drawFrame ()
{
Application.canvasContext.clearRect(0, 0, Application.canvas.width, Application.canvas.height);
for (var i = 0; i < Entity.entities.length; i++)
Entity.entities[i].draw();
}
}
/*
*/
Application.startTimestampMS = new Date().getTime();
Application.fps = 60; // Max is normally 60 (screen refresh rate, also the interval at which requestAnimationFrame is called)
Application.frameIntervalMS = 1000 / Application.fps;
Application.activeFrameCount = 0; // rename to updated or active FramesPerSecond
Application.activeFrameCountTimestampMS = Application.startTimestampMS;
Application.maxFrameCount = 0; // Screen refresh rate (hertz)
Application.currentFrameTimestampMS = Application.startTimestampMS;
Application.lastFrameTimestampMS = Application.currentFrameTimestampMS;
Application.progressMS = 0;
Application.progressPerSecondMS = 0;
Application.progressVariance = 1; // When progress is just under framerate interval, slightly lower framerate
/* $(window).resize (function ()
{
Game.HUD.resize();
}); */
Application.canvas = document.getElementById("applicationCanvas");
Application.canvasContext = Application.canvas.getContext("2d");
Application.FPS = document.getElementById("FPS");
Application.start();
/* ["mousedown", "touchdown"] */
Application.canvas.addEventListener("mousedown", function (event) {
//if (app.developmentMode)
//console.log(event);
// Calculate start point (considering screen event cords and target element margins, padding, positioning)
const rect = event.target.getBoundingClientRect();
let startX = event.clientX - rect.left; //x position within the element.
let startY = event.clientY - rect.top;
});
/* ["mousemove", "touch...?"] */
Application.canvas.addEventListener("mousemove", function (event) {
// Calculate hover point (considering screen event cords and target element margins, padding, positioning)
const rect = event.target.getBoundingClientRect();
let hoverX = event.clientX - rect.left; //x position within the element.
let hoverY = event.clientY - rect.top;
});
/* ["mouseup", "touchup"] */
Application.canvas.addEventListener("mouseup", function (event) {
// Calculate end point (considering screen event cords and target element margins, padding, positioning)
const rect = event.target.getBoundingClientRect();
let endX = event.clientX - rect.left; //x position within the element.
let endY = event.clientY - rect.top;
const shot = new Shot ([
{ x: 0, y: 0 },
{ x: 5, y: 0 },
{ x: 10, y: 10 },
{ x: 0, y: 10 },
]);
shot.position = { x: Application.player.position.x, y: Application.player.position.y };
shot.direction = { x: endX - Application.player.position.x, y: endY - Application.player.position.y };
});
</script>
</body>
</html></code>
<code><!DOCTYPE html> <html lang="en-GB"> <head> <title>asdf</title> </head> <body> <canvas id="applicationCanvas" width="1080" height="720"></canvas> <div id="FPS"></div> <script> // class Network { // connect to socket // manage messages // dic network objects // interface with objects and messages // } // class NetworkObject { // all variables // generate message from variables and send as a single string // } class Test { constructor (identifier) { let baseClass = this.constructor; let parentClass = "unset"; while ( parentClass != undefined ) { console.log ( baseClass.name ); if (baseClass.created == undefined) baseClass.created = {}; baseClass.created[identifier] = this; console.log(baseClass.created); parentClass = Object.getPrototypeOf ( baseClass ); baseClass = parentClass; if (parentClass == undefined) break; } //console.log(Test.created); } } class Test2 extends Test { constructor (identifier) { super(identifier); } } class Test3 extends Test2 { constructor (identifier) { super(identifier); } } new Test3("test"); class Vector2 { constructor (x = 0, y = 0) { this.x = x; this.y = y; } } class Entity { constructor () { Entity.entities.push(this); /* if (Entity.entities[] == undefined) Entity.entities[Object.getPrototypeOf (this.constructor).name] = []; if (Entity.entities[Object.getPrototypeOf (this.constructor).name][this.constructor.name] == undefined && [Object.getPrototypeOf (this.constructor).name] != this.constructor.name) Entity.entities[Object.getPrototypeOf (this.constructor).name][this.constructor.name] = {}; */ //Entity.entities[Object.getPrototypeOf (this.constructor).name][this.constructor.name].push(this); /* console.log(Object.getPrototypeOf (this.constructor).name); console.log(this.constructor.name); console.log (Entity.entities); */ } update () { /* on update position send request to udpate network data */ } /* networkUpdate() { application requests server update object (entity) has unique id application.entities.networkdata move local position towards network position } */ draw () { } } /* Entity */ Entity.entities = []; class Polygon extends Entity { constructor (vertices, moveable = false) { super(); this.position = new Vector2(0, 0); this.vertices = vertices; /* this.offsetVertices(); console.log("new vertices"); console.log(this.newVertices); this.movingVertices = []; this.movingVerticesTimer = 3; */ this.edges = []; this.moveable = moveable; this.velocity = { 65: { x: -25, y: 0 }, 68: { x: 25, y: 0 }, 87: { x: 0, y: -25 }, 83: { x: 0, y: 25 } } this.buildEdges(); this.index = Polygon.polygons.push(this); } update () { super.update(); //this.moveVertices(); if (this.moveable) { let fps = (Application.activeFrameCount / ((Application.currentFrameTimestampMS - Application.activeFrameCountTimestampMS) / 1000)); let deltaTime = fps / 1000; if (Input.keysDown[65]) { this.position.x += this.velocity[65].x * deltaTime; this.position.y += this.velocity[65].y * deltaTime; } if (Input.keysDown[68]) { this.position.x += this.velocity[68].x * deltaTime; this.position.y += this.velocity[68].y * deltaTime; } if (Input.keysDown[87]) { this.position.x += this.velocity[87].x * deltaTime; this.position.y += this.velocity[87].y * deltaTime; } if (Input.keysDown[83]) { this.position.x += this.velocity[83].x * deltaTime; this.position.y += this.velocity[83].y * deltaTime; } } } draw () { Application.canvasContext.lineWidth = 1; if (this.colliding) Application.canvasContext.strokeStyle = "#FF0000"; else Application.canvasContext.strokeStyle = "#000000"; Application.canvasContext.beginPath(); Application.canvasContext.moveTo( this.position.x + this.vertices[0].x, this.position.y + this.vertices[0].y );// + (Math.random() * (0.25 - 0.01) + 0.01) for (let i = 1; i < this.vertices.length; i++) { Application.canvasContext.lineTo( this.position.x + this.vertices[i].x, this.position.y + this.vertices[i].y ); } Application.canvasContext.closePath(); Application.canvasContext.stroke(); } buildEdges () { this.edges = []; if (this.vertices.length < 3) { console.error("Only polygons supported."); } for (let i = 0; i < this.vertices.length; i++) { const a = this.vertices[i]; let b = this.vertices[0]; if (i + 1 < this.vertices.length) { b = this.vertices[i + 1]; } this.edges.push({ x: (b.x - a.x), y: (b.y - a.y), }); } } projectInAxis (x, y) { let min = 10000000000; let max = -10000000000; for (let i = 0; i < this.vertices.length; i++) { let px = this.position.x + this.vertices[i].x; let py = this.position.y + this.vertices[i].y; var projection = (px * x + py * y) / (Math.sqrt(x * x + y * y)); if (projection > max) { max = projection; } if (projection < min) { min = projection; } } return { min, max }; } testWith (otherPolygon) { // get all edges const edges = []; for (let i = 0; i < this.edges.length; i++) { edges.push(this.edges[i]); } for (let i = 0; i < otherPolygon.edges.length; i++) { edges.push(otherPolygon.edges[i]); } let depth = Number.MAX_VALUE; let normal = new Vector2(); // build all axis and project for (let i = 0; i < edges.length; i++) { // get axis const length = Math.sqrt(edges[i].y * edges[i].y + edges[i].x * edges[i].x); const axis = { x: -edges[i].y / length, y: edges[i].x / length, }; // project polygon under axis const { min: minA, max: maxA } = this.projectInAxis(axis.x, axis.y); const { min: minB, max: maxB } = otherPolygon.projectInAxis(axis.x, axis.y); if (Polygon.intervalDistance(minA, maxA, minB, maxB) > 0) { return false; } let axisDepth = Math.min(maxB - minA, maxA - minB); if (axisDepth < depth) { depth = axisDepth; normal = axis; } } //depth /= 2; let magnitude = Math.sqrt((normal.x * normal.x) + (normal.y * normal.y)); normal.x /= magnitude; normal.y /= magnitude; this.position.x += -normal.x * depth / 2; this.position.y += -normal.y * depth / 2; otherPolygon.position.x += normal.x * depth / 2; otherPolygon.position.y += normal.y * depth / 2; let centerA = Polygon.findArithmeticMean(this.vertices); let centerB = Polygon.findArithmeticMean(otherPolygon.vertices); let direction = new Vector2(centerB.x - centerA.x, centerB.y - centerA.y); // dot if(direction.x * normal.x + direction.x * normal.x < 0) { normal = -normal; } return true; } onCollision(){ } static findArithmeticMean( vertices ) { let sumX = 0; let sumY = 0; for(var i = 0; i < vertices.length; i++) { let v = vertices[i]; sumX += v.X; sumY += v.Y; } return new Vector2(sumX / vertices.length, sumY / vertices.length); } moveVertices() { let fps = (Application.activeFrameCount / ((Application.currentFrameTimestampMS - Application.activeFrameCountTimestampMS) / 1000)); let deltaTime = fps / 1000; for(var i = 0; i < this.vertices.length; i++) { if (this.movingVertices[i] == undefined) { this.movingVertices[i] = new Vector2(); this.movingVertices[i].x = this.vertices[i].x; this.movingVertices[i].y = this.vertices[i].y; } this.movingVertices[i].x += (this.newVertices[i].x - this.movingVertices[i].x) * 0.1 * deltaTime; this.movingVertices[i].y += (this.newVertices[i].y - this.movingVertices[i].y) * 0.1 * deltaTime; // } /* if (this.movingVertices[i] == undefined) { this.movingVertices[i] = new Vector2(); this.movingVertices[i].x = this.newVertices[i].x; this.movingVertices[i].y = this.newVertices[i].y; } */ } /* for(var i = 0; i < this.vertices.length; i++) { if (this.movingVertices[i] == undefined) { */ /* this.movingVertices[i] = new Vector2(); this.movingVertices[i].x = this.newVertices[i].x; this.movingVertices[i].y = this.newVertices[i].y; } /* this.movingVertices[i].x += (this.newVertices[i].x - this.movingVertices[i].x) * 0.001 * deltaTime; this.movingVertices[i].y += (this.newVertices[i].y - this.movingVertices[i].y) * 0.001 * deltaTime; /* if (Math.abs(this.movingVertices[i].x - this.newVertices[i].x) < 1 && Math.abs(this.movingVertices[i].y - this.newVertices[i].y) < 1) { this.offsetVertices(i); } }*/ /* this.movingVerticesTimer -= deltaTime; if (this.movingVerticesTimer <= 0) { this.movingVerticesTimer = 10; this.offsetVertices(); this.movingVertices = this.newVertices; } */ } offsetVertices(index = undefined) { let singleIndex = true; if (index == undefined) { index = 0 singleIndex = false; } this.newVertices = []; for(; index < this.vertices.length; index++) { this.newVertices[index] = new Vector2(); this.newVertices[index].x = this.vertices[index].x + (Math.random() * ((50 - 1) + 1)); this.newVertices[index].y = this.vertices[index].y + (Math.random() * ((50 - 1) + 1)); if (singleIndex) break; } console.log("Set new vertices offset from original positions"); console.log(this.newVertices); } static intervalDistance(minA, maxA, minB, maxB) { if (minA < minB) { return (minB - maxA); } return (minA - maxB); } static testCollisions () { for (let i = 0; i < Polygon.polygons.length; i++) { for (let ii = 0; ii < Polygon.polygons.length; ii++) { if (i == ii) continue; if (Polygon.polygons[i].testWith(Polygon.polygons[ii])) { //console.log("Tested with index: ", i); Polygon.polygons[i].colliding = true; Polygon.polygons[ii].colliding = true; Polygon.polygons[i].onCollision(); Polygon.polygons[ii].onCollision(); // Add collisions array. If removing this collision results in 0 count then toggle colliding bool } else { Polygon.polygons[i].colliding = false; Polygon.polygons[ii].colliding = false; } } } } } Polygon.polygons = []; class Shot extends Polygon { constructor (vertices) { super(vertices); //this.position = { x: 10, y: 10 }; //this.direction = {}; } update() { //let fps = (Application.activeFrameCount / ((Application.currentFrameTimestampMS - Application.activeFrameCountTimestampMS) / 1000)); let deltaTime = Application.progressMS / 1000; this.position.x += this.direction.x * 0.25 * deltaTime; this.position.y += this.direction.y * 0.25 * deltaTime; } draw() { super.draw(); /* Application.canvasContext.lineWidth = 5; Application.canvasContext.strokeStyle = "#000000"; Application.canvasContext.beginPath(); Application.canvasContext.moveTo(this.position.x, this.position.y); Application.canvasContext.lineTo(this.position.x + 5, this.position.y + 5); Application.canvasContext.closePath(); Application.canvasContext.stroke(); */ } onCollision() { super.onCollision(); } } /* class Event { } */ class Input { constructor () { } } Input.keysDown = {}; document.addEventListener("keydown", function (event) { Input.keysDown[event.which] = true; console.log (Input.keysDown); }); document.addEventListener("keyup", function (event) { delete Input.keysDown[event.which]; // console.log (Input.keysDown); }); class Application { static start () { //Application.lineObject = new LineObject(); Application.player = new Polygon ([ { x: 0, y: 0 }, { x: 50, y: 0 }, { x: 50, y: 50 }, { x: 0, y: 50 }, ], true); new Polygon ([ { x: 0, y: 0 }, { x: 50, y: 0 }, { x: 50, y: 50 }, { x: 0, y: 50 }, ]); new Polygon ([ { x: 0, y: 0 }, { x: 50, y: 0 }, { x: 50, y: 50 }, { x: 0, y: 50 }, ]); new Polygon ([ { x: 12.5, y: 0 }, { x: 62.5, y: 100 }, { x: 0, y: 100 }, ]); Application.nextScreenFrame(); } static nextScreenFrame () { requestAnimationFrame (Application.screenFrame); } static screenFrame (frameTimestamp) { Application.currentFrameTimestampMS = new Date().getTime(); Application.progressMS = Application.currentFrameTimestampMS - Application.lastFrameTimestampMS; if (Application.progressMS >= Application.frameIntervalMS * Application.progressVariance) { Application.updateFrame(); Application.drawFrame(); Application.progressPerSecondMS += Application.progressMS; Application.progressVariance = 1 + (1 - (Application.progressMS / (Application.progressPerSecondMS / Application.activeFrameCount)) * 1.8); // In the event of a decrease in average progress, progress will already be less than the frame interval. No need to increase the frame interval. This does not really matter in execution, but is good practice. if (Application.progressVariance > 1) Application.progressVariance = 1; Application.lastFrameTimestampMS = Application.currentFrameTimestampMS; Application.activeFrameCount++; if (Application.activeFrameCount == Application.fps * 5) { Application.activeFrameCountTimestampMS = Application.currentFrameTimestampMS; Application.activeFrameCount = 1; Application.maxFrameCount = 1; Application.progressPerSecondMS = Application.progressMS; } if (Application.activeFrameCount > Application.fps) { Application.FPS.innerHTML = "FPS: " + (Application.activeFrameCount / ((Application.currentFrameTimestampMS - Application.activeFrameCountTimestampMS) / 1000)).toFixed (2) + "<br>Hertz: " + (Application.maxFrameCount / ((Application.currentFrameTimestampMS - Application.activeFrameCountTimestampMS) / 1000)).toFixed (2); } else { Application.FPS.innerHTML = "FPS: ...<br>Hertz: ..."; } /* if (Math.floor ((timestamp - startTime) / 1000) % 6 == 0) $(".log").html((actualFramesPerSecond / ((progressPerSecond) / 1000)).toFixed(1) + " / " + (possibleFramesPerSecond / ((progressPerSecond) / 1000)).toFixed(1)); if ((fillType == '-' && newFillPerc == 100) || (fillType == '+' && newFillPerc == 0)) { $(".log").html(timestamp - meterEmptyStartTimestamp); meterEmptyStartTimestamp = timestamp; } */ } Application.maxFrameCount++; Application.nextScreenFrame(); } static updateFrame () { for (var i = 0; i < Entity.entities.length; i++) Entity.entities[i].update(); Polygon.testCollisions(); } static drawFrame () { Application.canvasContext.clearRect(0, 0, Application.canvas.width, Application.canvas.height); for (var i = 0; i < Entity.entities.length; i++) Entity.entities[i].draw(); } } /* */ Application.startTimestampMS = new Date().getTime(); Application.fps = 60; // Max is normally 60 (screen refresh rate, also the interval at which requestAnimationFrame is called) Application.frameIntervalMS = 1000 / Application.fps; Application.activeFrameCount = 0; // rename to updated or active FramesPerSecond Application.activeFrameCountTimestampMS = Application.startTimestampMS; Application.maxFrameCount = 0; // Screen refresh rate (hertz) Application.currentFrameTimestampMS = Application.startTimestampMS; Application.lastFrameTimestampMS = Application.currentFrameTimestampMS; Application.progressMS = 0; Application.progressPerSecondMS = 0; Application.progressVariance = 1; // When progress is just under framerate interval, slightly lower framerate /* $(window).resize (function () { Game.HUD.resize(); }); */ Application.canvas = document.getElementById("applicationCanvas"); Application.canvasContext = Application.canvas.getContext("2d"); Application.FPS = document.getElementById("FPS"); Application.start(); /* ["mousedown", "touchdown"] */ Application.canvas.addEventListener("mousedown", function (event) { //if (app.developmentMode) //console.log(event); // Calculate start point (considering screen event cords and target element margins, padding, positioning) const rect = event.target.getBoundingClientRect(); let startX = event.clientX - rect.left; //x position within the element. let startY = event.clientY - rect.top; }); /* ["mousemove", "touch...?"] */ Application.canvas.addEventListener("mousemove", function (event) { // Calculate hover point (considering screen event cords and target element margins, padding, positioning) const rect = event.target.getBoundingClientRect(); let hoverX = event.clientX - rect.left; //x position within the element. let hoverY = event.clientY - rect.top; }); /* ["mouseup", "touchup"] */ Application.canvas.addEventListener("mouseup", function (event) { // Calculate end point (considering screen event cords and target element margins, padding, positioning) const rect = event.target.getBoundingClientRect(); let endX = event.clientX - rect.left; //x position within the element. let endY = event.clientY - rect.top; const shot = new Shot ([ { x: 0, y: 0 }, { x: 5, y: 0 }, { x: 10, y: 10 }, { x: 0, y: 10 }, ]); shot.position = { x: Application.player.position.x, y: Application.player.position.y }; shot.direction = { x: endX - Application.player.position.x, y: endY - Application.player.position.y }; }); </script> </body> </html></code>
<!DOCTYPE html>
<html lang="en-GB">
<head>
    <title>asdf</title>
    
</head>
<body>
    <canvas id="applicationCanvas" width="1080" height="720"></canvas>
    <div id="FPS"></div>
    <script>

//         class Network {
        
//     connect to socket
    
//     manage messages
    
//     dic network objects
    
//     interface with objects and messages
    
    
    
// }

// class NetworkObject {
        
//     all variables
    
//     generate message from variables and send as a single string
    
// }

class Test {

        constructor (identifier)
    {
        let baseClass = this.constructor;
        let parentClass = "unset";
        
        while (  parentClass != undefined  ) 
        {
                console.log ( baseClass.name );
            
            if (baseClass.created == undefined)
                    baseClass.created = {};
                
            baseClass.created[identifier] = this;
            
            console.log(baseClass.created);
            
                parentClass = Object.getPrototypeOf ( baseClass );
                baseClass = parentClass;
            
            if (parentClass == undefined)
                    break;
        }
        
        //console.log(Test.created);
    }
    
    
}

class Test2 extends Test {
        constructor (identifier)
    {
        super(identifier);
    }
}

class Test3 extends Test2 {
        constructor (identifier)
    {
        super(identifier);
    }
}

new Test3("test");

class Vector2 {

        constructor (x = 0, y = 0)
    {
            this.x = x;
        this.y = y;
    }

}

class Entity {

        constructor ()
    {
            Entity.entities.push(this);
        
        /* if (Entity.entities[] == undefined)
            Entity.entities[Object.getPrototypeOf (this.constructor).name] = [];
        
        if (Entity.entities[Object.getPrototypeOf (this.constructor).name][this.constructor.name] == undefined && [Object.getPrototypeOf (this.constructor).name] != this.constructor.name)
                Entity.entities[Object.getPrototypeOf (this.constructor).name][this.constructor.name] = {}; */
        
        //Entity.entities[Object.getPrototypeOf (this.constructor).name][this.constructor.name].push(this);
        
        /* console.log(Object.getPrototypeOf (this.constructor).name);
                console.log(this.constructor.name);
        console.log (Entity.entities); */
    }
    
        update () {
            /* on update position send request to udpate network data */
        
    }
    
   /*  networkUpdate() {
        application requests server update
        object (entity) has unique id application.entities.networkdata
        move local position towards network position
    } */
    
    draw () {
            
    }
    
} /* Entity */

Entity.entities = [];

class Polygon extends Entity {

        constructor (vertices, moveable = false) {
        
        super();
        
        this.position = new Vector2(0, 0);
            this.vertices = vertices;
        /* this.offsetVertices();
        console.log("new vertices");
        console.log(this.newVertices);
        this.movingVertices = [];
        this.movingVerticesTimer = 3; */
            this.edges = [];
        
        this.moveable = moveable;
        
        this.velocity = { 65: { x: -25, y: 0 }, 68: { x: 25, y: 0 }, 87: { x: 0, y: -25 }, 83: { x: 0, y: 25 } }
        
        this.buildEdges();
            
        this.index = Polygon.polygons.push(this);
        
    }
    
    update () {
    
            super.update();
        
        //this.moveVertices();
        
        if (this.moveable)
        {
            let fps = (Application.activeFrameCount / ((Application.currentFrameTimestampMS - Application.activeFrameCountTimestampMS) / 1000));
            let deltaTime = fps / 1000;

            if (Input.keysDown[65])
            {
                this.position.x += this.velocity[65].x * deltaTime;
                this.position.y += this.velocity[65].y * deltaTime;
            }

            if (Input.keysDown[68])
            {
                this.position.x += this.velocity[68].x * deltaTime;
                this.position.y += this.velocity[68].y * deltaTime;
            }

            if (Input.keysDown[87])
            {
                this.position.x += this.velocity[87].x * deltaTime;
                this.position.y += this.velocity[87].y * deltaTime;
            }

            if (Input.keysDown[83])
            {
                this.position.x += this.velocity[83].x * deltaTime;
                this.position.y += this.velocity[83].y * deltaTime;
            }
         }
    
    }
    
    draw () {
            
        Application.canvasContext.lineWidth = 1;
        if (this.colliding)
                Application.canvasContext.strokeStyle = "#FF0000";
        else
                Application.canvasContext.strokeStyle = "#000000";
        Application.canvasContext.beginPath();
        Application.canvasContext.moveTo(
            this.position.x + this.vertices[0].x,
            this.position.y + this.vertices[0].y
        );//  + (Math.random() * (0.25 - 0.01) + 0.01)
        for (let i = 1; i < this.vertices.length; i++) {
            Application.canvasContext.lineTo(
                this.position.x + this.vertices[i].x,
                this.position.y + this.vertices[i].y
            );
        }
        Application.canvasContext.closePath();
        Application.canvasContext.stroke();
    
    }
    
    buildEdges ()  {
            
        this.edges = [];
        
        if (this.vertices.length < 3) {
            console.error("Only polygons supported.");
        }
        
        for (let i = 0; i < this.vertices.length; i++) {
            const a = this.vertices[i];
            let b = this.vertices[0];
            if (i + 1 < this.vertices.length) {
                b = this.vertices[i + 1];
            }
            this.edges.push({
                x: (b.x - a.x),
                y: (b.y - a.y),
            });
        } 
        
    }
    
    projectInAxis (x, y) {
        let min = 10000000000;
        let max = -10000000000;
        for (let i = 0; i < this.vertices.length; i++) {
            let px = this.position.x + this.vertices[i].x;
            let py = this.position.y + this.vertices[i].y;
            var projection = (px * x + py * y) / (Math.sqrt(x * x + y * y));
            if (projection > max) {
                max = projection;
            }
            if (projection < min) {
                min = projection;
            }
        }
        return { min, max };
    }
        
    testWith (otherPolygon) {
        // get all edges
        const edges = [];
        for (let i = 0; i < this.edges.length; i++) {
            edges.push(this.edges[i]);
        }
        for (let i = 0; i < otherPolygon.edges.length; i++) {
            edges.push(otherPolygon.edges[i]);
        }
        
        let depth = Number.MAX_VALUE;
        let normal = new Vector2();
        
        // build all axis and project
        for (let i = 0; i < edges.length; i++) {
            // get axis
            const length = Math.sqrt(edges[i].y * edges[i].y + edges[i].x * edges[i].x);
            const axis = {
                x: -edges[i].y / length,
                y: edges[i].x / length,
            };
            // project polygon under axis
            const { min: minA, max: maxA } = this.projectInAxis(axis.x, axis.y);
            const { min: minB, max: maxB } = otherPolygon.projectInAxis(axis.x, axis.y);
            if (Polygon.intervalDistance(minA, maxA, minB, maxB) > 0) {
                return false;
            }
            
            let axisDepth = Math.min(maxB - minA, maxA - minB);
            
            if (axisDepth < depth)
            {
                    depth = axisDepth;
                normal = axis;
            }
        }
        
        //depth /= 2;
        
        let magnitude = Math.sqrt((normal.x * normal.x) + (normal.y * normal.y));
        normal.x /= magnitude;
        normal.y /= magnitude;
        
        this.position.x += -normal.x * depth / 2;
        this.position.y += -normal.y * depth / 2;
        
        otherPolygon.position.x += normal.x * depth / 2;
        otherPolygon.position.y += normal.y * depth / 2;
        
        let centerA = Polygon.findArithmeticMean(this.vertices);
        let centerB = Polygon.findArithmeticMean(otherPolygon.vertices);

        let direction = new Vector2(centerB.x - centerA.x, centerB.y - centerA.y);

                // dot
        if(direction.x * normal.x + direction.x * normal.x < 0)
        {
          normal = -normal;
        }
        
        return true;
    }
    
    onCollision(){
    
    }
    
    static findArithmeticMean( vertices )
    {
        let sumX = 0;
        let sumY = 0;

        for(var i = 0; i < vertices.length; i++)
        {
            let v = vertices[i];
            sumX += v.X;
            sumY += v.Y;
        }

        return new Vector2(sumX / vertices.length, sumY / vertices.length);
    }
    
    moveVertices() {
            let fps = (Application.activeFrameCount / ((Application.currentFrameTimestampMS - Application.activeFrameCountTimestampMS) / 1000));
        
        let deltaTime = fps / 1000;
        
        for(var i = 0; i < this.vertices.length; i++)
        {
                
                if (this.movingVertices[i] == undefined) {
                            
                                this.movingVertices[i] = new Vector2();
                            this.movingVertices[i].x = this.vertices[i].x;
                             this.movingVertices[i].y = this.vertices[i].y;
                            }
                                this.movingVertices[i].x += (this.newVertices[i].x - this.movingVertices[i].x) * 0.1 * deltaTime;
                            this.movingVertices[i].y += (this.newVertices[i].y - this.movingVertices[i].y) * 0.1 * deltaTime;
                           // } 
            
           /*  if (this.movingVertices[i] == undefined)
                    {
                this.movingVertices[i] = new Vector2();
                                this.movingVertices[i].x = this.newVertices[i].x;
                                this.movingVertices[i].y = this.newVertices[i].y; 
            } */
        }
        
        
            /* for(var i = 0; i < this.vertices.length; i++) {
                        if (this.movingVertices[i] == undefined)
                        { */
                    /* this.movingVertices[i] = new Vector2();
                                    this.movingVertices[i].x = this.newVertices[i].x;
                                    this.movingVertices[i].y = this.newVertices[i].y; 
                }
                
                /* this.movingVertices[i].x += (this.newVertices[i].x - this.movingVertices[i].x) * 0.001 * deltaTime;
                            this.movingVertices[i].y += (this.newVertices[i].y - this.movingVertices[i].y) * 0.001 * deltaTime; 
            
            /* if (Math.abs(this.movingVertices[i].x - this.newVertices[i].x) < 1 && Math.abs(this.movingVertices[i].y - this.newVertices[i].y) < 1)
            {
                this.offsetVertices(i);
            }  
        }*/
        
       /*  this.movingVerticesTimer -= deltaTime;
        if (this.movingVerticesTimer <= 0) {
            this.movingVerticesTimer = 10;
            this.offsetVertices();
            this.movingVertices = this.newVertices;
        } */
    }
    
    offsetVertices(index = undefined) {
    
            let singleIndex = true;
                    
            if (index == undefined)
                    {
                        index = 0
                        singleIndex = false;
                    }
                
        this.newVertices = [];
        
            for(; index < this.vertices.length; index++) {
                        this.newVertices[index] = new Vector2();
                
                        this.newVertices[index].x = this.vertices[index].x + (Math.random() * ((50 - 1) + 1));
                                            this.newVertices[index].y = this.vertices[index].y + (Math.random() * ((50 - 1) + 1));
                    
                    
                        if (singleIndex)
                            break;
                    }
         
         console.log("Set new vertices offset from original positions");
         console.log(this.newVertices);
    }
    
    static intervalDistance(minA, maxA, minB, maxB) {
        if (minA < minB) {
            return (minB - maxA);
        }
        return (minA - maxB);
    }
    
    static testCollisions () {
        for (let i = 0; i < Polygon.polygons.length; i++) {
                for (let ii = 0; ii < Polygon.polygons.length; ii++) {
                if (i == ii)
                    continue;
                if (Polygon.polygons[i].testWith(Polygon.polygons[ii])) {
                    //console.log("Tested with index: ", i);
                    Polygon.polygons[i].colliding = true;
                    Polygon.polygons[ii].colliding = true;
                    
                    Polygon.polygons[i].onCollision();
                    Polygon.polygons[ii].onCollision();
                    
                    // Add collisions array. If removing this collision results in 0 count then toggle colliding bool
                }
                else {
                        Polygon.polygons[i].colliding = false;
                    Polygon.polygons[ii].colliding = false;
                }
           }
        }
    }
    
}

Polygon.polygons = [];


class Shot extends Polygon {
        constructor (vertices) {
            super(vertices);
        //this.position = { x: 10, y: 10 };
        //this.direction = {};
    }
    
    update() {
            //let fps = (Application.activeFrameCount / ((Application.currentFrameTimestampMS - Application.activeFrameCountTimestampMS) / 1000));
        let deltaTime = Application.progressMS / 1000;
        
            this.position.x += this.direction.x * 0.25 * deltaTime;
        this.position.y += this.direction.y * 0.25 * deltaTime;
    }
    
    draw() {
            super.draw();
        
        /* Application.canvasContext.lineWidth = 5;
        Application.canvasContext.strokeStyle = "#000000";
        
        Application.canvasContext.beginPath();
        
        Application.canvasContext.moveTo(this.position.x, this.position.y);
        Application.canvasContext.lineTo(this.position.x + 5, this.position.y + 5);
            
        Application.canvasContext.closePath();
        
        Application.canvasContext.stroke(); */
    }
    
    onCollision() {
            super.onCollision();
    }
}

/* class Event {

} */

class Input {
  constructor () {

  }
}

Input.keysDown = {};

document.addEventListener("keydown", function (event) {
  Input.keysDown[event.which] = true;
  console.log (Input.keysDown);
});

document.addEventListener("keyup", function (event) {
  delete Input.keysDown[event.which];
  // console.log (Input.keysDown);
});

class Application 
{
        static start () {
            
        //Application.lineObject = new LineObject();
        Application.player = new Polygon ([
            { x: 0, y: 0 },
            { x: 50, y: 0 },
            { x: 50, y: 50 },
            { x: 0, y: 50 },
        ], true);
        new Polygon ([
            { x: 0, y: 0 },
            { x: 50, y: 0 },
            { x: 50, y: 50 },
            { x: 0, y: 50 },
        ]);
        new Polygon ([
            { x: 0, y: 0 },
            { x: 50, y: 0 },
            { x: 50, y: 50 },
            { x: 0, y: 50 },
        ]);
        new Polygon ([
                { x: 12.5, y: 0 },
            { x: 62.5, y: 100 },
            { x: 0, y: 100 },
        ]); 
        Application.nextScreenFrame(); 
                                                            
    }

    static nextScreenFrame () {
        requestAnimationFrame (Application.screenFrame);
    }
    
    static screenFrame (frameTimestamp)
    {
        Application.currentFrameTimestampMS = new Date().getTime();

        Application.progressMS = Application.currentFrameTimestampMS - Application.lastFrameTimestampMS;

        if (Application.progressMS >= Application.frameIntervalMS * Application.progressVariance) 
        {
            Application.updateFrame();
            Application.drawFrame();

            Application.progressPerSecondMS += Application.progressMS;
            Application.progressVariance = 1 + (1 - (Application.progressMS / (Application.progressPerSecondMS / Application.activeFrameCount)) * 1.8);
            
            // In the event of a decrease in average progress, progress will already be less than the frame interval. No need to increase the frame interval. This does not really matter in execution, but is good practice.
            if (Application.progressVariance > 1)
                    Application.progressVariance = 1;

            Application.lastFrameTimestampMS = Application.currentFrameTimestampMS;

            Application.activeFrameCount++;

            if (Application.activeFrameCount == Application.fps * 5)
            {
                Application.activeFrameCountTimestampMS = Application.currentFrameTimestampMS;
                Application.activeFrameCount = 1;
                Application.maxFrameCount = 1;
                Application.progressPerSecondMS = Application.progressMS;
            }

            if (Application.activeFrameCount > Application.fps)
            {
                Application.FPS.innerHTML = "FPS: " + (Application.activeFrameCount / ((Application.currentFrameTimestampMS - Application.activeFrameCountTimestampMS) / 1000)).toFixed (2) + "<br>Hertz: " + (Application.maxFrameCount / ((Application.currentFrameTimestampMS - Application.activeFrameCountTimestampMS) / 1000)).toFixed (2);
            }
            else
            {
                Application.FPS.innerHTML = "FPS: ...<br>Hertz: ...";
            }

            /* if (Math.floor ((timestamp - startTime) / 1000) % 6 == 0)
                $(".log").html((actualFramesPerSecond / ((progressPerSecond) / 1000)).toFixed(1) + " / " + (possibleFramesPerSecond / ((progressPerSecond) / 1000)).toFixed(1));
            
            if ((fillType == '-' && newFillPerc == 100) ||
                (fillType == '+' && newFillPerc == 0))
            {
                $(".log").html(timestamp - meterEmptyStartTimestamp);
                meterEmptyStartTimestamp = timestamp;
            } */
        }

        Application.maxFrameCount++;

        Application.nextScreenFrame();
    }
    
    static updateFrame ()
    {
            for (var i = 0; i < Entity.entities.length; i++)
                    Entity.entities[i].update();
            
        Polygon.testCollisions();
    }
    
    static drawFrame ()
    {
            Application.canvasContext.clearRect(0, 0, Application.canvas.width, Application.canvas.height);
        
                for (var i = 0; i < Entity.entities.length; i++)
                Entity.entities[i].draw();
    }

}

/*
*/
Application.startTimestampMS = new Date().getTime();

Application.fps = 60; // Max is normally 60 (screen refresh rate, also the interval at which requestAnimationFrame is called)
Application.frameIntervalMS = 1000 / Application.fps;
Application.activeFrameCount = 0; // rename to updated or active FramesPerSecond
Application.activeFrameCountTimestampMS = Application.startTimestampMS;
Application.maxFrameCount = 0; // Screen refresh rate (hertz)

Application.currentFrameTimestampMS = Application.startTimestampMS;
Application.lastFrameTimestampMS = Application.currentFrameTimestampMS;
Application.progressMS = 0;
Application.progressPerSecondMS = 0;
Application.progressVariance = 1; // When progress is just under framerate interval, slightly lower framerate

/* $(window).resize (function ()
                  {
  Game.HUD.resize();
}); */

Application.canvas = document.getElementById("applicationCanvas");
Application.canvasContext = Application.canvas.getContext("2d");

Application.FPS = document.getElementById("FPS");
Application.start();

/* ["mousedown", "touchdown"] */
Application.canvas.addEventListener("mousedown", function (event) {
  //if (app.developmentMode)
    //console.log(event);
    
  // Calculate start point (considering screen event cords and target element margins, padding, positioning)
  const rect = event.target.getBoundingClientRect();
  let startX = event.clientX - rect.left; //x position within the element.
  let startY = event.clientY - rect.top;
  
});

/* ["mousemove", "touch...?"] */
Application.canvas.addEventListener("mousemove", function (event) {
  // Calculate hover point (considering screen event cords and target element margins, padding, positioning)
  const rect = event.target.getBoundingClientRect();
  let hoverX = event.clientX - rect.left; //x position within the element.
  let hoverY = event.clientY - rect.top;
  
});

/* ["mouseup", "touchup"] */
Application.canvas.addEventListener("mouseup", function (event) {
  // Calculate end point (considering screen event cords and target element margins, padding, positioning)
  const rect = event.target.getBoundingClientRect();
  let endX = event.clientX - rect.left; //x position within the element.
  let endY = event.clientY - rect.top;
  
  const shot = new Shot ([  
                { x: 0, y: 0 },
                { x: 5, y: 0 },
                { x: 10, y: 10 },
                { x: 0, y: 10 },
            ]);
  shot.position = { x: Application.player.position.x, y: Application.player.position.y };
  shot.direction = { x: endX - Application.player.position.x, y: endY - Application.player.position.y };
});


    </script>
</body>
</html>

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật