Why is my sprite graphing algorithm sometimes leaving gaps while graphing? - javascript

My friends and I are working on a game. However, I've run into difficulty with the star sprite management. Sometimes, there are gaps (areas where sprites should be graphed but instead are not). I have included the entire program as a code snippet but below are also code blocks which are likely directly responsible for the problem.
There are coordinate pairs for each sprite. The coordinate map is presented below:
[{x:xCoordinate,y:yCoordinate},{x:xCoordinate,y:yCoordinate},...];
This code spawns and removes sprites...
for(var i = 0; i < stars.positions.length; i++){
//store coordinate positions...
var x = stars.positions[i].x;
var y = stars.positions[i].y;
//delete sprites no longer visible within the viewport...
if(x > window.innerWidth || x < -spriteWidth || y < -spriteHeight || y > window.innerHeight){
//sprite is no longer visible within viewport; remove it...
stars.positions.splice(i,1);
}
//find necessary comparative coordinates...
var lowestXCoordinatePair = stars.meta.lowestXCoordinatePair;
var highestXCoordinatePair = stars.meta.highestXCoordinatePair;
var lowestYCoordinatePair = stars.meta.lowestYCoordinatePair;
var highestYCoordinatePair = stars.meta.highestYCoordinatePair;
//gather star sprite meta data...
var spriteWidth = stars.meta.spriteWidth;
var spriteHeight = stars.meta.spriteHeight;
if(lowestXCoordinatePair.x > 0){
//Gap on the left side. New sprites necessary to fill the gap on left row...
//console.log('adding sprites left row...')
for(var i = 0; i < stars.meta.imagesYRequired; i++){
stars.positions.push({
x:lowestXCoordinatePair.x-spriteWidth,
y:lowestXCoordinatePair.y+i*spriteHeight
});
}
}
if(highestXCoordinatePair.x < window.innerWidth-spriteWidth){
//Gap on the right side. New sprites necessary to fill the gap on the right row...
//console.log('adding sprites right row...')
for(var i = 0; i < stars.meta.imagesYRequired; i++){
stars.positions.push({
x:highestXCoordinatePair.x+spriteWidth,
y:highestXCoordinatePair.y+i*spriteHeight
});
}
}
if(lowestYCoordinatePair.y > 0){
//Gap on the top side. New sprites necessary to fill the gap on the top row...
//console.log('adding sprites top row...')
for(var i = 0; i < stars.meta.imagesXRequired; i++){
stars.positions.push({
x:lowestYCoordinatePair.x+i*spriteWidth,
y:lowestYCoordinatePair.y-spriteHeight
});
}
}
if(highestYCoordinatePair.y < window.innerHeight-spriteHeight){
//Gap on the bottom side. New sprites necessary to fill the gap on the bottom row...
console.log('adding sprites bottom row...')
for(var i = 0; i < stars.meta.imagesXRequired; i++){
stars.positions.push({
x:highestYCoordinatePair.x+i*spriteWidth,
y:highestYCoordinatePair.y+spriteHeight
});
}
}
'use strict';
//global variables
var canvas, c;
//viewport variables
var viewportPosition = {
x:false,
y:false
};
//game matrix settings
var gameMatrixConfig = {
width:20000,
height:20000
};
//cursor position
var cursorPosition = {
x:false,
y:false
};
//spaceship position
var spaceship = {
x:false,
y:false,
rotation:0,
gameMatrixPositionX:false,
gameMatrixPositionY:false
};
//fps monitor (for monitoring frame rate for development purposes)...
var fps = {
lastFrameTime:undefined,
timeSinceLastFrame:undefined,
startDisplayTimeInterval:function(){
setInterval(function(){
document.getElementById('fpsLabel').innerHTML = Math.floor(1000/getTimeSinceLastFrame());
},500);
}
};
function getTimeSinceLastFrame(){
return fps.timeSinceLastFrame;
}
//resize throttle timer global variable holder
var resizeTimer = false;
//the drawing frame:
var renderFrame = false;
//global events
window.addEventListener('load',function(){
initialize('load');
});
window.addEventListener('resize',function(){
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function(){
initialize('resize');
},100);
});
window.addEventListener('mousemove',function(e){
cursorPosition.x = e.clientX;
cursorPosition.y = e.clientY;
});
//global functions
function initialize(type){
if(type == 'load'){
preLoadSprites();
}
initializeCanvas();
}
function initializeCanvas(){
canvas = document.getElementById('canvas');
c = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
c.width = canvas.width;
c.height = canvas.height;
}
/*a class with a filePath argument.
This class is used to create new sprite images
from an instance of the class.*/
function sprite(filePath){
//create a new image preload
this.sprite = new Image();
this.sprite.src = filePath;
//load status
this.loaded = false;
/*
holds all current positions of the sprite.
default start coordinates are 0,0
*/
this.positions = [];
//original associative array data structure...
//this.positions = {};
//the image has been preloaded
this.sprite.addEventListener('load',function(){
this.loaded = true;
/*
bind the "this" reference of the constructor to avoid referencing
the load event function
*/
}.bind(this));
}
function preLoadSprites(){
//create new objects for all the sprites to be used in the game
var sprites = {
stars:new sprite('https://drive.google.com/uc?export=download&id=0B821h2dKD0r_bERZb0RHeVRTQnM')
};
/*
check the load status of the sprites
loop through all properties of sprites object twice per second
check for load status. Load flag default is true. Set flag to false
when an unloaded image is discovered.
*/
var interval = setInterval(function(){
var x, loaded = true;
for(x in sprites){
if(!sprites[x].loaded){
loaded = false;
}
}
if(loaded){
clearInterval(interval);
//complete other tasks such as hiding a load spinner...
//ready sprites and context for graphing and provide access to sprites....
initializeGraphing(sprites);
}
},50);
}
function initializeGraphing(sprites){
//set initial viewport position in game matrix....
viewportPosition.x = gameMatrixConfig.width/2;
viewportPosition.y = gameMatrixConfig.height/2;
//start graph animation loop; provide access to sprites....
graph(sprites);
}
/*research how to inherit values from another object with
local variables.*/
function graph(sprites){
updateSpritePositions(sprites);
//testing frame rate...
setInterval(function(){draw()},16.67);
//60fps interval code. Uncomment when testing is complete...
//setInterval(function(){draw()},16.67);
//calculate sprite requirements for viewport and configure a sprite matrix...
initializeStars(sprites.stars);
fps.startDisplayTimeInterval();
//render the graphic frame
function draw(){
//console.log(fps.timeSinceLastFrame)
fps.timeSinceLastFrame = Date.now()-fps.lastFrameTime;
//fps.displayTimeInterval();
//fps.lastFrameTime = Date.now();
//clear the canvas
c.clearRect(0,0,window.innerWidth,window.innerHeight);
//graph the stars...
graphStars(sprites.stars);
updateSpritePositions(sprites);
fps.lastFrameTime = Date.now();
}
}
function initializeStars(stars){
/*
calculate sprite requirements for viewport and configure a sprite matrix
this only needs to happen once unless the viewport is resized
*/
/*
meta data used for various calculations throughout the script...
*/
stars.meta = {
//required sprites to fill the viewport width (based on sprite width)...
imagesXRequired:Math.ceil(window.innerWidth/(stars.sprite.width))+2,
//required sprites to fill the viewport height (based on sprite height)...
imagesYRequired:Math.ceil(window.innerHeight/(stars.sprite.height))+2,
//required sprites to fill the entire viewport...
get requiredSprites(){
return this.imagesXRequired*this.imagesYRequired;
},
//the sprite width...
spriteWidth:stars.sprite.width,
//the sprite height...
spriteHeight:stars.sprite.height,
//the lowest x value in stars.positions...
get lowestXCoordinatePair(){
var xCoordinates = [];
var yCoordinates = [];
for(var i = 0; i < stars.positions.length; i++){
xCoordinates.push(stars.positions[i].x);
yCoordinates.push(stars.positions[i].y);
}
var x = Math.min.apply(Math, xCoordinates);
var index = xCoordinates.indexOf(x);
var y = yCoordinates[index];
return {
x:x,
y:y
};
},
//the highest x value in stars.positions...
get highestXCoordinatePair(){
var xCoordinates = [];
var yCoordinates = [];
for(var i = 0; i < stars.positions.length; i++){
xCoordinates.push(stars.positions[i].x);
yCoordinates.push(stars.positions[i].y);
}
var x = Math.max.apply(Math, xCoordinates);
var index = xCoordinates.indexOf(x);
var y = yCoordinates[index];
return {
x:x,
y:y
};
},
//the lowest y value in stars.positions...
get lowestYCoordinatePair(){
var xCoordinates = [];
var yCoordinates = [];
for(var i = 0; i < stars.positions.length; i++){
xCoordinates.push(stars.positions[i].x);
yCoordinates.push(stars.positions[i].y);
}
var y = Math.min.apply(Math, yCoordinates);
var index = yCoordinates.indexOf(y);
var x = xCoordinates[index];
return {
x:x,
y:y
};
},
//the highest y value in stars.positions...
get highestYCoordinatePair(){
var xCoordinates = [];
var yCoordinates = [];
for(var i = 0; i < stars.positions.length; i++){
xCoordinates.push(stars.positions[i].x);
yCoordinates.push(stars.positions[i].y);
}
var y = Math.max.apply(Math, yCoordinates);
var index = yCoordinates.indexOf(y);
var x = xCoordinates[index];
return {
x:x,
y:y
};
}
};
//the y coordinate in a scaled matrix system for sprites...
var y = 0;
//the x coordinate in a scaled matrix system for sprites...
var x = 0;
//loop through the number of required sprites and graph...
for(var i = 0; i < stars.meta.requiredSprites; i++){
//calculate when a new row is necessary
if((i)%stars.meta.imagesXRequired == 0){
x = 0;
y++;
}
//set actual starting viewport matrix coordinate positions....
stars.positions[i] = {
x:x*stars.meta.spriteWidth,
y:y*stars.meta.spriteWidth
};
x++;
}
}
function graphStars(stars){
/*
prior to graphing, determine if a sprite is no longer within
the viewport matrix and remove it...
if new sprites are necessary, add new coordinates accordingly...
*/
/*
==============IMPORTANT NOTE==================
*/
for(var i = 0; i < stars.positions.length; i++){
//store coordinate positions...
var x = stars.positions[i].x;
var y = stars.positions[i].y;
//delete sprites no longer visible within the viewport...
if(x > window.innerWidth || x < -spriteWidth || y < -spriteHeight || y > window.innerHeight){
//sprite is no longer visible within viewport; remove it...
stars.positions.splice(i,1);
}
//find necessary comparative coordinates...
var lowestXCoordinatePair = stars.meta.lowestXCoordinatePair;
var highestXCoordinatePair = stars.meta.highestXCoordinatePair;
var lowestYCoordinatePair = stars.meta.lowestYCoordinatePair;
var highestYCoordinatePair = stars.meta.highestYCoordinatePair;
//gather star sprite meta data...
var spriteWidth = stars.meta.spriteWidth;
var spriteHeight = stars.meta.spriteHeight;
if(lowestXCoordinatePair.x > 0){
//Gap on the left side. New sprites necessary to fill the gap on left row...
//console.log('adding sprites left row...')
for(var i = 0; i < stars.meta.imagesYRequired; i++){
stars.positions.push({
x:lowestXCoordinatePair.x-spriteWidth,
y:lowestXCoordinatePair.y+i*spriteHeight
});
}
}
if(highestXCoordinatePair.x < window.innerWidth-spriteWidth){
//Gap on the right side. New sprites necessary to fill the gap on the right row...
//console.log('adding sprites right row...')
for(var i = 0; i < stars.meta.imagesYRequired; i++){
stars.positions.push({
x:highestXCoordinatePair.x+spriteWidth,
y:highestXCoordinatePair.y+i*spriteHeight
});
}
}
if(lowestYCoordinatePair.y > 0){
//Gap on the top side. New sprites necessary to fill the gap on the top row...
//console.log('adding sprites top row...')
for(var i = 0; i < stars.meta.imagesXRequired; i++){
stars.positions.push({
x:lowestYCoordinatePair.x+i*spriteWidth,
y:lowestYCoordinatePair.y-spriteHeight
});
}
}
if(highestYCoordinatePair.y < window.innerHeight-spriteHeight){
//Gap on the bottom side. New sprites necessary to fill the gap on the bottom row...
console.log('adding sprites bottom row...')
for(var i = 0; i < stars.meta.imagesXRequired; i++){
stars.positions.push({
x:highestYCoordinatePair.x+i*spriteWidth,
y:highestYCoordinatePair.y+spriteHeight
});
}
}
c.drawImage(stars.sprite,x,y);
}
}
function updateViewportPosition(){
}
function updateSpritePositions(sprites){
/*gather information from the cursor to influence the next frame data
cursor is a local object variable template for use within this function only
cursor stores information about the cursor's position
*/
var cursor = {
distance:{
//the cursor's distances on the planes from the origin for X and Y
cursorXDistance:Math.abs(window.innerWidth/2-cursorPosition.x),
cursorYDistance:Math.abs(window.innerHeight/2-cursorPosition.y)
},
quadrant:function(){
//method returns the appropriate quadrant number for the unit circle
if(cursorPosition.x > window.innerWidth/2 && cursorPosition.y < window.innerHeight/2){
//first quadrant
return 1;
}
if(cursorPosition.x > window.innerWidth/2 && cursorPosition.y > window.innerHeight/2){
//fourth quadrant
return 4;
}
if(cursorPosition.x < window.innerWidth/2 && cursorPosition.y < window.innerHeight/2){
//second quadrant
return 2;
}
if(cursorPosition.x < window.innerWidth/2 && cursorPosition.y > window.innerHeight/2){
//third quadrant
return 3;
}
}
};
//calculate the velocity (the number of pixels to move for the next frame)...
function velocity(){
/*
To calculate velocity ratio, divide the hypotenuse of the cursor's position
by the viewport hypotenuse.
*/
return Math.sqrt(Math.pow(cursor.distance.cursorXDistance,2) + Math.pow(cursor.distance.cursorYDistance,2))/100;
}
//calculate the movement ratio: the number of x pixels per y pixels
function movementRatio(){
var xRatio = cursor.distance.cursorXDistance/(cursor.distance.cursorYDistance+cursor.distance.cursorXDistance);
var yRatio = cursor.distance.cursorYDistance/(cursor.distance.cursorYDistance+cursor.distance.cursorXDistance);
return {xRatio,yRatio};
}
//update positions of sprites...
//retrieve the current movement ratio object...
var coordinateChange = movementRatio();
//retrieve the current quadrant of the unit circle for the cursor's position...
var quadrant = cursor.quadrant();
//retrieve velocity coefficient...
var velocity = velocity();
//update viewport position based on quadrant position...
var i;
for(i in sprites.stars.positions){
if(quadrant == 1){
//update star sprite position
sprites.stars.positions[i].x -= coordinateChange.xRatio*velocity;
sprites.stars.positions[i].y += coordinateChange.yRatio*velocity;
}
if(quadrant == 2){
//update star sprite position
sprites.stars.positions[i].x += coordinateChange.xRatio*velocity;
sprites.stars.positions[i].y += coordinateChange.yRatio*velocity;
}
if(quadrant == 3){
//update the star sprite position
sprites.stars.positions[i].x += coordinateChange.xRatio*velocity;
sprites.stars.positions[i].y -= coordinateChange.yRatio*velocity;
}
if(quadrant == 4){
//update star sprite position
sprites.stars.positions[i].x -= coordinateChange.xRatio*velocity;
sprites.stars.positions[i].y -= coordinateChange.yRatio*velocity;
}
}
}
html,body{
width:100%;
height:100%;
margin:0;
padding:0;
}
canvas{
width:100%;
height:100%;
margin-bottom:-8px;
background:#434343;
}
#gameInterface{
width:100%;
height:100%;
}
.hidden{
width:1px;
height:1px;
position:fixed;
top:0;
left:0;
z-index:-100;
}
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Canvas Game v0.35</title>
<!--<link rel="stylesheet" href="css/index.css" type="text/css">-->
<!--<script type="text/javascript" src="js/index.js"></script>-->
</head>
<body>
<div id="fpsLabel" style="position:fixed; top:0; left:0; display:table; background:#fff;">
</div>
<div id="intro">
</div>
<div id="gameInterface">
<canvas id="canvas"></canvas>
</div>
</body>
</html>
I've brainstormed a list of possible causes for the problem:
Gaps are caused by sprites being removed too soon.
Gaps are caused by sprites not being spawned.
(sub item) Spawning conditional is correct but the loop to create a new row is flawed.
(sub item) Spawning conditional is flawed.
Could someone help me understand why there are occasionally gaps and help me understand how to fix it? Thank you.

Related

Multiple html canvas element with random graphics on same page

Trying to create multiple canvas element on a single html page and then draw different graphics on each of them . But the issue is , the same graphics is drawn on all of them without any randomization
canvas_container_div = document.getElementById('canvas_container_div');
let animation_frame_id;
let canvas_array = [];
let context_array = [];
let number_of_canvas = 5;
//creating canvas and storing it in an array
for(var i = 0;i < number_of_canvas ; i++){
var canvas1 = document.createElement('canvas');
canvas1.style.width = '200px';
canvas1.style.height = '200px';
canvas1.style.margin = '10px';
canvas1.style.border = '1px solid white';
canvas_array.push(canvas1);
}
//displaying all canvas inside the div element
for(var i = 0;i < canvas_array.length ; i++){
canvas_container_div.appendChild(canvas_array[i]);
}
//getting all the contex for all the canvas
for(var i = 0;i < canvas_array.length ; i++){
context_array.push(canvas_array[i].getContext('2d'));
}
//random values generating
let hue = Math.random()*360;
//or other radom parameters
//updating each graphics
function update(ctx){
ctx.fillStyle = 'hsl('+hue+',100%,50%)';
}
function render(){
//getting all the context
for(var i = 0;i < context_array.length ; i++){
//clearing bg for perticular canvas
context_array[i].clearRect(0,0,canvas_array[i].width,canvas_array[i].height);
//passing perticular canvas context to update method
update(context_array[i]);
//drawing with pertucular context
context_array[i].beginPath();
context_array[i].arc(canvas_array[i].width/2,canvas_array[i].height/2,40,0,Math.PI *2);
context_array[i].closePath();
context_array[i].fill();
}
animation_frame_id = requestAnimationFrame(render);
}
render();
Wanted to have different color for all the circle drawn on different canvas , but all the circles are of same color . cannot randomize
You are defining your hue outside of your renders for loop, so each time it runs the loop and calls on the hue, it is getting the same color for the fill style. You could create a function that randomizes the hue inside a for loop to to create an array of colors that is the same length as your canvas array.
Then in your for loop within the render function call on the array and its index to make each individual circle a different color fill style.
NOTE: This only randomizes the colors, it does not check if a color already exists, so you may want additional code to check if a number is already in the array before pushing the value into the array. Furthermore, you will notice some hues randomize within a certain number close enough to each other that they actually look like they are the same, you could also include code within the setHue() function that checks to see if the the numbers are within a certain restraint of each other, this would likely be a .include() or even a conditional that checks the difference between the hue array and the current value within the loop.
let hue = [];
function setHue() {
for (let h = 0; h < context_array.length; h++) {
hue.push(Math.trunc(Math.random() * 360));
}
}
setHue();
canvas_container_div = document.getElementById('canvas_container_div');
let animation_frame_id;
let canvas_array = [];
let context_array = [];
let number_of_canvas = 5;
//creating canvas and storing it in an array
for (var i = 0; i < number_of_canvas; i++) {
var canvas1 = document.createElement('canvas');
canvas1.style.width = '200px';
canvas1.style.height = '200px';
canvas1.style.margin = '10px';
canvas1.style.border = '1px solid white';
canvas_array.push(canvas1);
}
//displaying all canvas inside the div element
for (var i = 0; i < canvas_array.length; i++) {
canvas_container_div.appendChild(canvas_array[i]);
}
//getting all the contex for all the canvas
for (var i = 0; i < canvas_array.length; i++) {
context_array.push(canvas_array[i].getContext('2d'));
}
//Randomize your hue value and make an array to hold the value
//Then in your for loop within the render function call on the
//array and its index to make each individual circle a different color fill style
let hue = [];
function setHue() {
for (let h = 0; h < context_array.length; h++) {
let color = Math.trunc(Math.random() * 360);
hue.push(color);
}
console.log(hue);
}
setHue();
function render() {
//getting all the context
for (var i = 0; i < context_array.length; i++) {
//clearing bg for perticular canvas
context_array[i].clearRect(0, 0, canvas_array[i].width, canvas_array[i].height);
//drawing with particular context
context_array[i].beginPath();
context_array[i].arc(canvas_array[i].width / 2, canvas_array[i].height / 2, 40, 0, Math.PI * 2);
context_array[i].closePath();
context_array[i].fill();
context_array[i].fillStyle = 'hsl(' + hue[i] + ',100%,50%)';
}
animation_frame_id = requestAnimationFrame(render);
}
render();
<div id="canvas_container_div"></div>

Chrome Performance Issue With Bitmap Area Sampling Effect (JavaScript)

I am writing an HTML5 game using the engine Phaser, in which I am implementing what are essentially live backgrounds, backgrounds that respond to the movements of the game objects. The first I am working with is a water ripple effect that uses area sampling on the bitmapData object. I thought I had a performance issue in my code, but it turns out that Firefox runs it like a dream. Chrome runs a little slower to begin with and slows to less than 10 FPS when my game objects go too close to the top or bottom of the screen. (I am at a loss for why that makes a difference.)
This thread suggests that Chrome has poor image processing performance and suggests to break large image data up into smaller pieces. I don't know if this is possible in my case, because this is not simply an image displaying on the screen but an effect based on pixels next to each other that refreshes each frame. Even if it is possible, I think Chrome would end up having to do the same amount of work or more to get the four individual bitmaps to interact with each other as if they were one.
I've been doing performance tests in Chrome for a few hours, and the issue is definitely that it is getting caught up on the method that actually creates the effect by reading pixels from a source imageData and writing them to another location in a target imageData (the ws.displace(x,y) method below).
function waterStage(canvas) {
var ws = new Object();
ws.dampFactor = 16;
ws.magFactor = 150;
ws.dispFactor = 0.5;
ws.lumFactor = 1;
ws.width = canvas.width;
ws.height = canvas.height;
// Initialize height data caches
ws.pMaps = [];
var map1 = new Array(ws.width+2);
var map2 = new Array(ws.width+2);
for (x=0; x < map1.length; x++) {
map1[x] = new Array(ws.height+2);
map2[x] = new Array(ws.height+2);
}
for (x=0; x < map1.length; x++) {
for (y=0; y < map1[x].length; y++) {
map1[x][y] = 0;
map2[x][y] = 0;
}
}
ws.pMaps.push(map1, map2);
ws.stageInit = function(canvas) {
canvas.fill(100,100,100);
canvas.ctx.strokeStyle = "#000000";
canvas.ctx.lineWidth = 2;
canvas.ctx.moveTo(0,0);
for (y=0; y < ws.height; y+=10) {
canvas.ctx.beginPath();
canvas.ctx.moveTo(0,y);
canvas.ctx.lineTo(ws.width,y);
canvas.ctx.closePath();
canvas.ctx.stroke();
}
ws.sourceData = canvas.ctx.getImageData(0, 0, ws.width, ws.height);
ws.targetData = canvas.ctx.getImageData(0, 0, ws.width, ws.height);
}
ws.setWave = function(pnt) {
ws.pMaps[0][pnt.x-1][pnt.y-1] = ws.magFactor//*pnt.magnitude;
}
ws.resolveWaves = function(x,y) {
// Calculate the net result of the wave heights
ws.pMaps[1][x][y] = ((ws.pMaps[0][x-1][y]+ws.pMaps[0][x+1][y]+ws.pMaps[0][x][y-1]+ws.pMaps[0][x][y+1]) / 2)
-ws.pMaps[1][x][y];
ws.pMaps[1][x][y] -= (ws.pMaps[1][x][y]/ws.dampFactor);
}
ws.displace = function(x,y) {
var displace = Math.floor(ws.pMaps[1][x][y]*ws.dispFactor);
var xCorrect = x-1, yCorrect = y-1;
var targetIndex = (xCorrect + yCorrect * ws.width)*4;
if (displace == 0) {
ws.targetData.data[targetIndex] = ws.sourceData.data[targetIndex];
ws.targetData.data[targetIndex+1] = ws.sourceData.data[targetIndex+1];
ws.targetData.data[targetIndex+2] = ws.sourceData.data[targetIndex+2];
}
else {
if (displace < 0) {
displace += 1;
}
var sourceX = displace+xCorrect;
var sourceY = displace+yCorrect;
var sourceIndex = (sourceX + sourceY * ws.width)*4;
//var lum = ws.pMaps[1][x][y]*ws.lumFactor;
ws.targetData.data[targetIndex] = ws.sourceData.data[sourceIndex];//+lum;
ws.targetData.data[targetIndex+1] = ws.sourceData.data[sourceIndex+1];//+lum;
ws.targetData.data[targetIndex+2] = ws.sourceData.data[sourceIndex+2];//+lum;
}
}
ws.stageRefresh = function(moves, canvas) {
canvas.clear();
for (j=0; j < moves.length; j++) {
ws.setWave(moves[j]);
}
for (x=1; x <= ws.width; x++) {
if (ws.pMaps[1][x][0] != 0 || ws.pMaps[0][x][0] != 0) {
alert("TOP ROW ANOMALY");
}
for (y=1; y <= ws.height; y++) {
ws.resolveWaves(x,y);
ws.displace(x,y);
}
}
ws.pMaps.sort(function(a,b) { return 1 });
//ws.pMaps[0] = ws.pMaps[1];
//ws.pMaps[1] = temp;
canvas.ctx.putImageData(ws.targetData, 0, 0);
}
return ws;
}
canvas is the bitmapData that is given as the texture for the background (not an HTML5 canvas; sorry if that's confusing). ws.stageRefresh(moves,canvas) is called on every frame update.
Before I try to make the split-into-four-bitmaps solution work, does anyone have any guidance for other ways to improve the performance of this effect on Chrome?

drawImage with Canvas is sending image to the back of the canvas [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
I have been doing a little experimenting with canvas, creating drawings using lines shapes text etc, and inserting .png files. The inserting .png files is the bit that I cannot get to work.
Edit:
Undesired behaviour of this code: I load shapes to the graphics context, then load an image file to the graphics context, however when the graphics context is drawn, the image is at behind the shapes, despite being drawn last.
I wanted the image file to be at the top, in front of the shapes.
Desired behaviour: To bring image file to the front of the canvas, so it is not hidden by shapes drawn in the graphics context.
function loadImage(name) {
images[name] = new Image();
images[name].src = "DogWalking/" + name + ".png";
images[name].onload = function() {
graphics.drawImage(this, 0, 300);
canvas.bringToFront(this);
};
}
the function for drawing is called here:
function draw() {
graphics.save(); // to make sure changes don't carry over from one call to the next
graphics.fillStyle = "transparent"; // background color
graphics.fillRect(0,0,wWidth, wHeight);
graphics.fillStyle = "black";
applyLimits(graphics,xleft,xright,ytop,ybottom,true);
graphics.lineWidth = pixelSize;
world.draw(graphics);
graphics.drawImage(images["dog-walking11"],200,200);
graphics.restore();
}
code for the whole page is
<!DOCTYPE html>
<html>
<meta charset="UTF-8">
<head>
<title>Hierarchical Modeling 2D</title>
<style>
#messagediv {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 0;
background-color: indigo;
}
#canvasdiv {
position: absolute;
left: 0;
top: 0;
z-index: 10;
background-color: transparent;
}
</style>
<script type="text/javascript" src="rgbcolor.js"></script>
<script type="text/javascript">
"use strict";
var totalResources = 17;
var numResourcesLoaded = 0;
var images = {};
function loadImage(name) {
images[name] = new Image();
images[name].src = "DogWalking/" + name + ".png";
images[name].onload = function() {
//
graphics.drawImage(this, 0, 300);
canvas.bringToFront(this);
}
}
var canvas; // DOM object corresponding to the canvas
var graphics; // 2D graphics context for drawing on the canvas
var ctx; // 2D graphics context for drawing on the canvas
var myNumber = 0, myNumber2 = 0, myInterval, myInterval2, myelement, thisdiv, printx;
var mycoords = new Array();
var pcoords = new Array(); //coordinates of the portal.
//var pcoords = [[0,0], [50,300], [250,150]]; //coordinates of the portal.
var nocoords = 2;
var frameNumber = 0; // Current frame number.
var frameNumber2 = 0;
var sun;
var sun2;
var ground;
var world;
var pixelSize;
var wWidth;
var wHeight;
var portals = new Array("calendar1","alternativsearch","art1", "directory1");
var portalsval = new Array();
var portalsobj;
var leftj = new Array(3,1,4,2);
var forwards = "http://www.alternativworld.com";
// ---------------- Set Page Layout ----------------
// function to set size of canvas and location of portals
function pageLayout() {
var w = window, d = document, e = d.documentElement, g = d.getElementsByTagName('body')[0];
wWidth = w.innerWidth || e.clientWidth || g.clientWidth;
wHeight = w.innerHeight|| e.clientHeight|| g.clientHeight;
// Adjust wWidth and wHeight if ratio does not match scenary 7 by 5.
if (wWidth/wHeight != 7/5)
if (wWidth/wHeight > 7/5) {
var widthPortion = 5 * wWidth/wHeight;
wWidth = wWidth * 7 / widthPortion;
} else {
var heightPortion = 7 * wHeight/wWidth;
wHeight = wHeight * 5 / heightPortion;
}
var widthheight, localerror = false;
widthheight = Math.min(wWidth, wHeight);
if(widthheight < 400){
var localerror = true;
}
if (localerror == true)
alert("Warning, the page size of your browser or your screen resolution may be too small to correctly view this web page.");
var theCanvas = d.getElementById("theCanvas");
theCanvas.height = wHeight;
theCanvas.width = wWidth;
}
//Function to listen to the mouse events and see if a link is selected.
function doMouseDown(evt) {
var r = canvas.getBoundingClientRect();
var x = Math.round(evt.clientX - r.left);
var y = Math.round(evt.clientY - r.top);
alert(evt.clientX+ " " + evt.clientY);
for (var i = portals.length+1; i >= 0; i--) {
var p = pcoords[i];
if (Math.abs(p[0] - x) <= 50 && Math.abs(p[1] - y) <= 50) {
document.location.href = forwards;
return;
} else if (Math.abs(0 - x) <= 50 && Math.abs(0 - y) <= 50){
document.location.href = "http://www.alternativeuk.co.uk";
return;
}
}
}
// ---------------- The object-oriented scene graph API ------------------
/**
* The base class for all nodes in the scene graph data structure.
*/
function SceneGraphNode() {
this.fillColor = null; // If non-null, the default fillStyle for this node.
this.strokeColor = null; // If non-null, the default strokeStyle for this node.
}
SceneGraphNode.prototype.doDraw = function(g) {
// This method is meant to be abstract and must be
// OVERRIDDEN in any actual object in the scene graph.
// It is not meant to be called; it is called by draw().
throw "doDraw not implemented in SceneGraphNode"
}
SceneGraphNode.prototype.draw = function(g) {
// This method should be CALLED to draw the object
// represented by this SceneGraphNode. It should NOT
// ordinarily be overridden in subclasses.
graphics.save();
if (this.fillColor) {
g.fillStyle = this.fillColor;
}
if (this.strokeColor) {
g.strokeStyle = this.strokeColor;
}
this.doDraw(g);
graphics.restore();
}
SceneGraphNode.prototype.setFillColor = function(color) {
// Sets fillColor for this node to color.
// Color should be a legal CSS color string, or null.
this.fillColor = color;
return this;
}
SceneGraphNode.prototype.setStrokeColor = function(color) {
// Sets strokeColor for this node to color.
// Color should be a legal CSS color string, or null.
this.strokeColor = color;
return this;
}
SceneGraphNode.prototype.setColor = function(color) {
// Sets both the fillColor and strokeColor to color.
// Color should be a legal CSS color string, or null.
this.fillColor = color;
this.strokeColor = color;
return this;
}
/**
* Defines a subclass, CompoundObject, of SceneGraphNode to represent
* an object that is made up of sub-objects. Initially, there are no
* sub-objects.
*/
function CompoundObject() {
SceneGraphNode.call(this); // do superclass initialization
this.subobjects = []; // the list of sub-objects of this object
}
CompoundObject.prototype = new SceneGraphNode(); // (makes it a subclass!)
CompoundObject.prototype.add = function(node) {
// Add node a subobject of this object. Note that the
// return value is a reference to this node, to allow chaining
// of method calls.
this.subobjects.push(node);
return this;
}
CompoundObject.prototype.doDraw = function(g) {
// Just call the sub-objects' draw() methods.
for (var i = 0; i < this.subobjects.length; i++)
this.subobjects[i].draw(g);
}
/**
* Define a subclass, TransformedObject, of SceneGraphNode that
* represents an object along with a modeling transformation to
* be applied to that object. The object must be specified in
* the constructor. The transformation is specified by calling
* the setScale(), setRotate() and setTranslate() methods. Note that
* each of these methods returns a reference to the TransformedObject
* as its return value, to allow for chaining of method calls.
* The modeling transformations are always applied to the object
* in the order scale, then rotate, then translate.
*/
function TransformedObject(object) {
SceneGraphNode.call(this); // do superclass initialization
this.object = object;
this.rotationInDegrees = 0;
this.scaleX = 1;
this.scaleY = 1;
this.translateX = 0;
this.translateY = 0;
}
TransformedObject.prototype = new SceneGraphNode(); // (makes it a subclass!)
TransformedObject.prototype.setRotation = function(angle) {
// Set the angle of rotation, measured in DEGREES. The rotation
// is always about the origin.
this.rotationInDegrees = angle;
return this;
}
TransformedObject.prototype.setScale = function(sx, sy) {
// Sets scaling factors.
this.scaleX = sx;
this.scaleY = sy;
return this;
}
TransformedObject.prototype.setTranslation = function(dx,dy) {
// Set translation mounts.
this.translateX = dx;
this.translateY = dy;
return this;
}
TransformedObject.prototype.doDraw = function(g) {
// Draws the object, with its modeling transformation.
g.save();
if (this.translateX != 0 || this.translateY != 0) {
g.translate(this.translateX, this.translateY);
}
if (this.rotationInDegrees != 0) {
g.rotate(this.rotationInDegrees/180*Math.PI);
}
if (this.scaleX != 1 || this.scaleY != 1) {
g.scale(this.scaleX, this.scaleY);
}
this.object.draw(g);
g.restore();
}
/**
* A subclass of SceneGraphNode representing filled triangles.
* The constructor specifies the vertices of the triangle:
* (x1,y1), (x2,y2), and (x3,y3).
*/
function Triangle(x1,y1,x2,y2,x3,y3) {
SceneGraphNode.call(this);
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.x3 = x3;
this.y3 = y3;
}
Triangle.prototype = new SceneGraphNode();
Triangle.prototype.doDraw = function(g) {
g.beginPath();
g.moveTo(this.x1,this.y1);
g.lineTo(this.x2,this.y2);
g.lineTo(this.x3,this.y3);
g.closePath();
g.fill();
}
/**
* Directly create a line object as a SceneGraphNode with a
* custom doDraw() method. line is of length 1 and
* extends along the x-axis from (0,0) to (1,0).
*/
var line = new SceneGraphNode();
line.doDraw = function(g) {
g.beginPath();
g.moveTo(0,0);
g.lineTo(1,0);
g.stroke();
}
/**
* Directly create a filled rectangle object as a SceneGraphNode with a
* custom doDraw() method. filledRect is a square with side 1, centered
* at (0,0), with corners at (-0.5,-0.5) and (0.5,0.5).
*/
var filledRect = new SceneGraphNode();
filledRect.doDraw = function(g) {
g.fillRect(-0.5,-0.5,1,1);
}
/**
* Directly create a rectangle object as a SceneGraphNode with a
* custom doDraw() method. rect is a square with side 1, centered
* at (0,0), with corners at (-0.5,-0.5) and (0.5,0.5). Only the
* outline of the square is drawn.
*/
var rect = new SceneGraphNode();
rect.doDraw = function(g) {
g.strokeRect(-0.5,-0.5,1,1);
}
/**
* Directly create a filled circle object as a SceneGraphNode with a
* custom doDraw() method. filledCircle is a circle with radius 0.5
* (diameter 1), centered at (0,0).
*/
var filledCircle = new SceneGraphNode();
filledCircle.doDraw = function(g) {
g.beginPath();
g.arc(0,0,0.5,0,2*Math.PI);
g.fill();
}
var clickHere = new SceneGraphNode();
clickHere.doDraw = function(g) {
g.fillText("click here :)",0,0)
}
/**
* Directly create a circle object as a SceneGraphNode with a
* custom doDraw() method. filledCircle is a circle with radius 0.5
* (diameter 1), centered at (0,0). Only the outline of the circle
* is drawn.
*/
var circle = new SceneGraphNode();
circle.doDraw = function(g) {
g.beginPath();
g.arc(0,0,0.5,0,2*Math.PI);
g.stroke();
}
var dog = new SceneGraphNode();
dog.doDraw = function(g) {
g.drawImage(images["dog-walking11"],-2, 2);
alert(images["dog-walking11"].name);
}
// -------------------- Specific to this application ----------------------------
/*
* Define two extra basic objects as SceneGraphNodes with custom doDraw() methods.
* One represents the ground, the other a vane for a windmill.
*/
var ground = new SceneGraphNode();
ground.doDraw = function(g) {
g.beginPath();
g.moveTo(0,-1);
g.lineTo(0,0.8);
g.lineTo(1.5,1.65);
g.lineTo(1.8,1.3);
g.lineTo(3,2.1);
g.lineTo(4.7,0.7);
g.lineTo(6.1,1.2);
g.lineTo(7,0.8);
g.lineTo(7,-1);
g.closePath();
g.fill();
}
var windmillVane = new SceneGraphNode();
windmillVane.doDraw = function(g) {
g.beginPath();
g.moveTo(0,0);
g.lineTo(0.5,0.1);
g.lineTo(1.5,0);
g.lineTo(0.5,-0.1);
g.closePath();
g.fill();
}
var world; // A SceneGraphNode representing the entire picture. This should
// be created in the createWorld() method.
var pixelSize; // The size of one pixel, in the transformed coordinates.
// This is used as the default width of a stroke.
var background = "#C8C8FF"; // A CSS color string giving the background color.
// the draw() function fills the canvas with this color.
var xleft = 0; // The requested xy-limits on the canvas, after the
var xright = 7; // coordinate transformation has been applied.
var ybottom = -1; // The transformation is applied in the draw() function.
var ytop = 4;
var frameNumber = 0; // Current frame number.
var cart; // TransformedObjects that are animated.
var wheel;
var sun;
var clickText1;
var clickText2;
var rotor;
/**
* Create the scene graph data structure. The global variable world must
* refer to the root node of the scene graph. This function is called in
* the init() function.
*/
function createWorld() {
pageLayout();
var i;
var sunTemp = new CompoundObject();
sunTemp.setColor("yellow"); // color for filled circle and light rays
for (i = 0; i < 12; i++) { // add the 12 light rays, with different rotations
sunTemp.add( new TransformedObject(line).setScale(0.75,0.75).setRotation(i*30) );
}
sunTemp.add( filledCircle ); // the face of the sun
sunTemp.add( new TransformedObject(circle).setColor("#B40000") ); // outlines the face
sun = new TransformedObject(sunTemp);
clickText1 = new TransformedObject(clickHere).setColor("#B40000").setScale(0.01,-0.01);
var wheelTemp = new CompoundObject();
wheelTemp.setColor("black"); // color for all but one of the subobjects
wheelTemp.add( new TransformedObject(filledCircle).setScale(2,2) );
wheelTemp.add( new TransformedObject(filledCircle).setScale(1.6,1.6).setColor("#CCCCCC") );
wheelTemp.add( new TransformedObject(filledCircle).setScale(0.4,0.4) );
for (i = 0; i < 12; i++) { // add the 12 spokes
wheelTemp.add( new TransformedObject(line).setRotation(i*30) );
}
wheel = new TransformedObject(wheelTemp);
var cartTemp = new CompoundObject();
cartTemp.setColor("red"); // color for the rectangular body of the cart
cartTemp.add( new TransformedObject(wheel).setScale(0.8,0.8).setTranslation(1.5,-0.1) );
cartTemp.add( new TransformedObject(wheel).setScale(0.8,0.8).setTranslation(-1.5,-0.1) );
cartTemp.add( new TransformedObject(filledRect).setScale(5,2).setTranslation(0,1) ); // the body of the cart
cart = new TransformedObject(cartTemp).setScale(0.3,0.3);
clickText2 = new TransformedObject(clickHere).setColor("yellow").setScale(0.01,-0.01);
var rotorTemp = new CompoundObject(); // a "rotor" consisting of three vanes
rotorTemp.setColor( "#C86464" ); // color for all of the vanes
rotorTemp.add( windmillVane );
rotorTemp.add( new TransformedObject(windmillVane).setRotation(120) );
rotorTemp.add( new TransformedObject(windmillVane).setRotation(240) );
rotor = new TransformedObject(rotorTemp);
var windmill = new CompoundObject();
windmill.setColor("#E0C8C8"); // color for the pole
windmill.add( new TransformedObject(filledRect).setScale(0.1,3).setTranslation(0,1.5) ); // the pole
windmill.add( new TransformedObject(rotor).setTranslation(0,3) ); // the rotating vanes
world = new CompoundObject();
world.setColor("#00961E"); // color used for the ground only
world.add(ground);
//world.add( new TransformedObject(filledRect).setScale(7,0.8).setTranslation(3.5,0).setColor("#646496") ); // road
//world.add( new TransformedObject(filledRect).setScale(7,0.06).setTranslation(3.5,0).setColor("white") ); // line in road
world.add( new TransformedObject(windmill).setScale(0.6,0.6).setTranslation(0.75,1) );
world.add( new TransformedObject(windmill).setScale(0.4,0.4).setTranslation(2.2,1.3) );
world.add( new TransformedObject(windmill).setScale(0.7,0.7).setTranslation(3.7,0.8) );
world.add( new TransformedObject(sun).setTranslation(5.5,3.3) );
world.add( new TransformedObject(clickText1).setTranslation(5.25,3.3) );
world.add( cart );
world.add( clickText2 );
//alert(2);
}
/**
* This will be called before each frame is drawn.
*/
function updateFrame() {
frameNumber++;
if (frameNumber>= 312){
frameNumber = 0;
frameNumber2 = 1;
}
cart.setTranslation(-3 + 13*(frameNumber % 300) / 300.0, 0);
clickText2.setTranslation(-3.3 + 13*(frameNumber % 300) / 300.0, 0.25);
if (typeof(pcoords[5]) != 'undefined') {
pcoords[5][0] = (-3.3 + 13*(frameNumber % 300) / 300.0-xleft)*canvas.width / (xright-xleft);
pcoords[5][1] = (0.25-ytop)*canvas.height / (ybottom-ytop);
}
wheel.setRotation(-frameNumber*3.1);
sun.setRotation(-frameNumber);
rotor.setRotation(frameNumber * 2.7);
}
// ------------------------------- graphics support functions --------------------------
/**
* Draw one frame of the animation. Probably doesn't need to be changed,
* except maybe to change the setting of preserveAspect in applyLimits().
*/
function draw() {
graphics.save(); // to make sure changes don't carry over from one call to the next
graphics.fillStyle = "transparent"; // background color
graphics.fillRect(0,0,wWidth, wHeight);
graphics.fillStyle = "black";
applyLimits(graphics,xleft,xright,ytop,ybottom,true);
graphics.lineWidth = pixelSize;
world.draw(graphics);
graphics.drawImage(images["dog-walking11"],200,200);
graphics.restore();
}
/**
* Applies a coordinate transformation to the graphics context, to map
* xleft,xright,ytop,ybottom to the edges of the canvas. This is called
* by draw(). This does not need to be changed.
*/
//pcoords[0][0] =
//pcoords[0][1]=
function applyLimits(g, xleft, xright, ytop, ybottom, preserveAspect) {
var width = canvas.width; // The width of this drawing area, in pixels.
var height = canvas.height; // The height of this drawing area, in pixels.
var k = portals.length;
var j;
var i = 0, widthheight, myradius;
var localerror = false;
if (pcoords.length < k) {
while (portals[i]){
j = i + 1;
if (width > 100){
var rWidth = width/(k + 1);
rWidth= Math.floor(rWidth);
} else {
var lWidth = 0;
var rWidth = 0;
}
if (height > 100){
var bHeight = height/(k + 1);
bHeight= Math.floor(bHeight);
} else {
var tHeight = 0;
var bHeight = 0;
}
var myleft = leftj[i] * rWidth - 50;
var mytop = j * bHeight - 50;
pcoords[i]= new Array;
pcoords[i][0] = myleft;
pcoords[i][1] = mytop;
i = i + 1;
}
}
if (preserveAspect) {
// Adjust the limits to match the aspect ratio of the drawing area.
var displayAspect = Math.abs(height / width);
var requestedAspect = Math.abs(( ybottom-ytop ) / ( xright-xleft ));
var excess;
if (displayAspect > requestedAspect) {
excess = (ybottom-ytop) * (displayAspect/requestedAspect - 1);
ybottom += excess/2;
ytop -= excess/2;
}
else if (displayAspect < requestedAspect) {
excess = (xright-xleft) * (requestedAspect/displayAspect - 1);
xright += excess/2;
xleft -= excess/2;
}
}
var pixelWidth = Math.abs(( xright - xleft ) / width);
var pixelHeight = Math.abs(( ybottom - ytop ) / height);
pixelSize = Math.min(pixelWidth,pixelHeight);
if (frameNumber == 4 || frameNumber == 5){
pcoords.push([(5.25-xleft)*width / (xright-xleft),(3.3-ytop)*height / (ybottom-ytop)]);
pcoords.push([(-3.3 + 13*(frameNumber % 300) / 300.0-xleft)*width / (xright-xleft), (0.25-ytop)*height / (ybottom-ytop)]);
}
g.scale( width / (xright-xleft), height / (ybottom-ytop) );
g.translate( -xleft, -ytop );
// if (frameNumber < 3)
}
//------------------ Animation framework ------------------------------
var animationTimeout = null; // A null value means the animation is off.
// Otherwise, this is the timeout ID.
function frame() {
// Draw one frame of the animation, and schedule the next frame.
updateFrame();
draw();
canvas.addEventListener("mousedown", doMouseDown, false);
animationTimeout = setTimeout(frame, 33);
}
function setAnimationRunning(run) {
if ( run ) {
if (animationTimeout == null) {
// If the animation is not already running, start
// it by scheduling a call to frame().
animationTimeout = setTimeout(frame, 33);
}
}
else {
if (animationTimeout != null) {
// If the animation is running, stop it by
// canceling the next scheduled call to frame().
clearTimeout(animationTimeout);
}
animationTimeout = null; // Indicates that animation is off.
}
}
//----------------------- initialization -------------------------------
function init() {
try {
canvas = document.getElementById("theCanvas");
if(typeof G_vmlCanvasManager != 'undefined') {
canvas = G_vmlCanvasManager.initElement(canvas);
}
graphics = canvas.getContext("2d");
}
catch (e) {
document.getElementById("message").innerHTML =
"Sorry, this page requires canvas graphics, but<br>" +
"it looks like your browser does not support it<br>" +
"Reported error: " + e;
return;
}
// add any other necessary initialization
document.getElementById("animateCheck").checked = true; // Make sure box is checked!
loadImage("dog-walking11");
createWorld();
setAnimationRunning(true); // start the animation
}
</script>
</head>
<body onload="init()" style="background-color: rgb(220,220,220)">
<div id="messagediv">
<h2>Hierarchical Modeling Example</h2>
<!-- For error reporting: the contents of the noscript tag are
shown only if JavaScript is not available. The paragraph with
id="message" is for reporting errors using JavaScript.-->
<noscript><b>This page requires JavaScript, which is<br>
not enabled in your browser.</b></noscript>
<p id="message" style="color:red"></p>
<p><input type="checkbox" id="animateCheck" onchange="setAnimationRunning(this.checked)">
<label for="animateCheck">Run Animation</label>
</p>
</div>
<div id="canvasdiv">
<canvas id="theCanvas" width= "400" height= "300"
style="background-color: transparent"></canvas>
</div>
</body>
</html>
It seems that I had to use graphics.drawImage() after using graphics.restore().
Though I was trying to draw the image in the correct order (after), compared to drawing the rectangles, circles etc, the shapes did not come out of the buffer onto the page until after the restore function().
When I was calling drawImage, I assumed that it was loading it onto the buffered canvas with the rest of the things, ready to be drawn in the correct order, when in fact it was putting it straight onto the page.
It seems strange since it was being called with the graphics context, so I thought it would be added to the rest of the canvas graphics, but i appear to be wrong.

Drawing in canvas becomes slower and slower when it draws more stuff during loop

I am trying to incrementally draw 3 lines which are 120 degrees from each other from a point using html5 canvas. The vertex of each lines will become another 3 new center point and spawns another 3 lines at each center and it repeats this..
My problem is, the incremental speed becomes slower and slower (or the drawing becomes slower) as more items are drawn. (maybe something happens in my code but I am not quite familiar how canvas exactly works...). You can copy the code and run it in your local browser to see what I means.
Please see my code (it is very easy to understand) and tell me what causes this.
<!DOCTYPE HTML>
<html>
<head>
<style>
body {
margin: 0px;
padding: 0px;
}
</style>
</head>
<body>
<canvas id="canvas" ></canvas>
<script>
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
canvas.width= window.innerWidth;
canvas.height= window.innerHeight;
// some staring test values
var centerPt={x:canvas.width/2,y:canvas.height/2};
var radius=100;
var angle=0;
// calculate the 3 endpoints at 120 degree separations
var endPt000=anglePoint(centerPt,90);
var endPt120=anglePoint(centerPt,210);
var endPt240=anglePoint(centerPt,330);
var length = 0;
var maxLength = 100;
var centreSet = new Array();
centreSet = getCentres();
var counter = 0;
var end = centreSet.length;
init();
function init() {
start(centreSet[0].x, centreSet[0].y);
}
function start(myX, myY) {
centerPt.x = myX;
centerPt.y = myY;
animate(centerPt, length);
}
function animate(centerPt,length) {
// update
// clear
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw stuff
draw(centerPt,length);
length = length + 1;
// request new frame
if(length < maxLength){
requestAnimFrame(function() {
animate(centerPt,length);
});
}
else{
if(counter < end){
counter = counter + 1;
centerPt.x = centreSet[counter].x;
centerPt.y = centreSet[counter].y;
endPt000=anglePoint(centerPt,90);
endPt120=anglePoint(centerPt,210);
endPt240=anglePoint(centerPt,330);
length = 0;
setTimeout(function(){animate(centerPt, length);},600);
}
}
}
// draw a red center dot
// draw 3 blue endpoint dots
// draw 3 lines from center going slider% of the way to the endpoints
function draw(centerPt,sliderValue){
var pct=sliderValue;
ctx.clearRect(0,0,canvas.width,canvas.height);
line(centerPt,pointAtPercent(centerPt,endPt000,pct),"green");
line(centerPt,pointAtPercent(centerPt,endPt120,pct),"green");
line(centerPt,pointAtPercent(centerPt,endPt240,pct),"green");
}
// calc XY at the specified angle off the centerpoint
function anglePoint(centerPt,degrees){
var x=centerPt.x-radius*Math.cos( degrees*Math.PI/180 );
var y=centerPt.y-radius*Math.sin( degrees*Math.PI/180 );
return({x:x,y:y});
}
// just draw a line from point1 to point2
function line(pt1,pt2,color){
// ctx.beginPath();
ctx.moveTo(pt1.x,pt1.y);
ctx.lineTo(pt2.x,pt2.y);
ctx.strokeStyle=color;
ctx.lineWidth=2;
ctx.stroke();
}
// calc XY which is a specified percent distance from pt1 to pt2
function pointAtPercent(pt1,pt2,sliderValue) {
// calculate XY at slider% towards pt2
var x = pt1.x + (pt2.x-pt1.x) * sliderValue/100;
var y = pt1.y + (pt2.y-pt1.y) * sliderValue/100;
return({x:x,y:y});
}
//the following are used to get all the center points...
function getCentres() {
var x = window.innerWidth/2;
var y = window.innerHeight/2;
centreSet[0] = centerPt;
var ref = 0;
var end = 0;
var b = true;
var tempCenter = centerPt;
for(var j = 0; j < 5; j++){
tempCenter = centreSet[ref];
end = end + 1;
centreSet[end] = anglePoint(tempCenter,90);
end = end + 1;
centreSet[end] = anglePoint(tempCenter,210);
end = end + 1;
centreSet[end] = anglePoint(tempCenter,330);
ref = ref+1;
}
return centreSet;
}
</script>
</body>
</html>
The problem is you are appending and appending the path. This means that each time you call stroke() the new line together with all the old lines are stroked. You won't see this clearly as the old lines are drawn on top in the same location. And as more and more lines are added the more time it takes to stroke them..
To prevent this you need to break the path. Do this with beginPath().
If you activate your out-commented line it should work fine:
function line(pt1,pt2,color){
ctx.beginPath(); //<-- activate this
ctx.moveTo(pt1.x,pt1.y);
ctx.lineTo(pt2.x,pt2.y);
ctx.strokeStyle=color;
ctx.lineWidth=2;
ctx.stroke();
}

How to draw a simple 2D grid (non interactive) in PaperJS?

I have an interactive application mockup made with PaperJS but it still lacks a small feature. I need to draw a 2D grid (you know... that uniform mesh of lines that repeat endlessly over a surface), it will be used as guides for user interactions when dragging things over the screen (but the grid itself can be completely static).
I just don't know how to implement it in PaperJS. It can't be just a background image since it will be presented in different scales, also I wanted it to be rendered very fast since it will always be visible.
The type of grid I would like to draw is a 2D mesh centered grid, like in the example (a) of this picture:
Any enlightenment is welcome.
If all you want is lines:
var drawGridLines = function(num_rectangles_wide, num_rectangles_tall, boundingRect) {
var width_per_rectangle = boundingRect.width / num_rectangles_wide;
var height_per_rectangle = boundingRect.height / num_rectangles_tall;
for (var i = 0; i <= num_rectangles_wide; i++) {
var xPos = boundingRect.left + i * width_per_rectangle;
var topPoint = new paper.Point(xPos, boundingRect.top);
var bottomPoint = new paper.Point(xPos, boundingRect.bottom);
var aLine = new paper.Path.Line(topPoint, bottomPoint);
aLine.strokeColor = 'black';
}
for (var i = 0; i <= num_rectangles_tall; i++) {
var yPos = boundingRect.top + i * height_per_rectangle;
var leftPoint = new paper.Point(boundingRect.left, yPos);
var rightPoint = new paper.Point(boundingRect.right, yPos);
var aLine = new paper.Path.Line(leftPoint, rightPoint);
aLine.strokeColor = 'black';
}
}
drawGridLines(4, 4, paper.view.bounds);
If you want each rectangle to be a separate Path to hitTest for the individual rectangles:
var drawGridRects = function(num_rectangles_wide, num_rectangles_tall, boundingRect) {
var width_per_rectangle = boundingRect.width / num_rectangles_wide;
var height_per_rectangle = boundingRect.height / num_rectangles_tall;
for (var i = 0; i < num_rectangles_wide; i++) {
for (var j = 0; j < num_rectangles_tall; j++) {
var aRect = new paper.Path.Rectangle(boundingRect.left + i * width_per_rectangle, boundingRect.top + j * height_per_rectangle, width_per_rectangle, height_per_rectangle);
aRect.strokeColor = 'white';
aRect.fillColor = 'black';
}
}
}
drawGridRects(4, 4, paper.view.bounds);

Categories