Can I not use an object in an object for drawImage()? - javascript

I can't seem to figure out why my code doesn't work. What I'm essentially trying to do is generate a 10x10 tile based map using arrays.
The idea is to create an object called Box with an 'x' and 'y' axis property and also an image object within the box. Then, each position in the 2d array is populated with a box object.
I then want to draw all these arrays out on a canvas. Each tile(or array element) is a 64x64 box.
const ROW = 10;
const COLS = 11;
const SIZE = 64;
var canvas = document.getElementById("canvas");
var surface = canvas.getContext("2d");
//creating tile
function box() {
this.xaxis = 56;
this.yaxis = 0;
this.img = new Image();
this.img.src = "box_image.png";
}
//creating map
var map =[];
function setMap() {
for (var i = 0; i < ROW; i++) {
for (var o = 0; o < COLS; o++) {
map[i][o] = new box();
}
}
}
//rendering map
function render() {
for (var i = 0; i < map.length; i++) {
for (var x = 0; x < map.length; x++) {
var tile = map[i][x];
tile.xaxis *= i;
tile.yaxis *= x;
surface.drawImage(tile.img, tile.xaxis, tile.yaxis, 64, 64);
}
}
}
setTimeout(render, 10);

Adding a few elements you forgot, here's how I would do it.
Fiddle
HTML
<canvas id="canvas" width="1000" height="1000"></canvas>
<!-- set canvas size -->
JS
const ROW = 10;
const COLS = 11;
const SIZE = 64;
var canvas = document.getElementById("canvas");
var surface = canvas.getContext("2d");
//creating tile
function box() {
this.xaxis = 56;
this.yaxis = 0;
this.src = "https://cdn4.iconfinder.com/data/icons/video-game-adicts/1024/videogame_icons-01-128.png"; //save path to image
}
//creating map
var map =[];
function setMap() {
for (var i = 0; i < ROW; i++) {
var arr = []; //make new row
map.push(arr); //push new row
for (var o = 0; o < COLS; o++) {
map[i].push(new box()); //make and push new column element in current row
}
}
}
//rendering map
function render() {
for (var i = 0; i < ROW; i++) { //For each row
for (var x = 0; x < COLS; x++) { //And each column in it
var tile = map[i][x];
tile.xaxis *= i;
tile.yaxis += (x*SIZE); //increment y value
var img = new Image();
img.onload = (function(x,y) { //draw when image is loaded
return function() {
surface.drawImage(this, x, y, 64, 64);
}
})(tile.xaxis, tile.yaxis);
img.src = tile.src;
}
}
}
setMap(); //create the grid
render(); //render the grid

There are a number of errors in your code.
First you are loading the same image 110 times. Load it once and that will save a lot of memory and time.
You create a single dimetioned array map
map = [];
Then attempt to access to as a two dim map. map[i][o] that will not work. You need to create a new array for each row.
You create the function to populate the map setMap() but you never call the function.
The Boxes you create have the yaxis value set to 0. When you call render and multiply it by the column index the result will be zero, so you will only see one column of images. You need to set the yaxis value to some value (64)
Below is your code fixed up with some comments. I left the zero yaxis value as maybe that is what you wanted. The image is created only once and the onload event is used to call render When setMap is called I add a new array for each row. I call setMap at the bottom but can be called anytime after you declare and define var map = [];
const ROW = 10;
const COLS = 11;
const SIZE = 64;
const canvas = document.getElementById("canvas");
const surface = canvas.getContext("2d");
const image = new Image();
image.src = "box_image.png";
// onload will not fire until all the immediate code has finished running
image.onload = function(){render()}; // call render when the image has loaded
//creating tile
function Box() { // any function you call with new should start with a capital
this.xaxis = 56;
this.yaxis = 0; // should this not be something other than zero
this.img = image;
}
//creating map
const map =[];
function setMap() {
for (var i = 0; i < ROW; i++) {
var row = []; // new array for this row
map[i] = row;
for (var o = 0; o < COLS; o++) {
row[o] = new box();
}
}
}
//rendering map
function render() {
for (var i = 0; i < map.length; i++) {
for (var x = 0; x < map[i].length; x++) { // you had map.length you needed the array for row i which is map[i]
var tile = map[i][x];
tile.xaxis *= i;
tile.yaxis *= x; // Note you have zero for yaxis?? 0 times anything is zero
surface.drawImage(tile.img, tile.xaxis, tile.yaxis, 64, 64);
}
}
}
setMap(); // create the map

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>

three.js memory leak .dispose() not working / wrong usage

Good day!
Have issue with memory handling. I read lots of forums but still can't find whats wrong with my code.
I'm working on project where I combine d3.js with three.js to visualize nodes like planets on orbits in space.
I have a lot of data - like 8K planets in 8+ orbits. But when I try to load new data - I can't destroy current tree without memory leak.
I would be grateful for any help! Here is part of code where I create planets and where I try to destroy them:
function initTree(root) {
var start, end;
var nodes = tree.nodes(root); //this is d3.js tree init
var depth = getDepth(root);
var first_x_offset = nodes[0].x;
if (isNaN(first_x_offset)) {first_x_offset = 0}
//create orbits
var orbitTexture = new THREE.ImageUtils.loadTexture('img/orbit_texture.png');
var orbitMaterial = new THREE.MeshBasicMaterial({map: orbitTexture, transparent:true, side: THREE.DoubleSide, alphaTest: 0.05, opacity:0.3});
var sphereGeometry = new THREE.SphereGeometry(1, 6, 6);
var orbitSize = 30;
for (var k=1; k<depth; k++) {
var orbit = new THREE.Mesh(new THREE.CircleGeometry(orbitSize*k, 64), orbitMaterial);
orbit.rotation.x = -90*Math.PI/180;
orbit.name = 'orbit';
scene.add(orbit);
}
//end orbits
//camera position
camera.position.x = 0;
camera.position.y = 70;
camera.position.z = -orbitSize*depth-100;
controls.target.x = 0;
controls.target.y = 0;
controls.target.z = 0;
camera.up.x = 0;
camera.up.y = 1;
camera.up.z = 0;
//this is parent object to place in center
var parent = new THREE.Object3D();
parent.name = 'parent';
scene.add(parent);
y=0;
spheres = {};
objects = [];
nodes.forEach(function(d) {
if (d.type == 'BLANK') {return}
d.x = d.x - first_x_offset;
if (isNaN(d.x)) {d.x = 0}
var sphereMaterial = new THREE.MeshLambertMaterial({color: 0xdddddd, wireframe: false, opacity: 0.7, transparent: true});
var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial );
sphere.material.color.setHex(color_type[d.type]);
sphere.castShadow = false; //maybe change to true
sphere.id2 = y;
d.id2 = y;
sphere.d = d;
sphere.scale.x = radius_type[d.type];
sphere.scale.y = radius_type[d.type];
sphere.scale.z = radius_type[d.type];
sphere.name = 'sphere';
spheres[y] = sphere;
//count items of each type
count_type[d.type]++;
//how many nodes in tree
y++;
//create pivot
var pivot = new THREE.Object3D;
//rotate it
pivot.rotation.y = d.x*Math.PI/180-90;
//append to parent
pivot.name = 'pivot';
parent.add(pivot);
//add mesh to pivot
var default_distance = size/(depth-1);
if (d.y > 0) {
d.y = (Math.round(d.y/default_distance)) * (orbitSize-8.8);
}
sphere.position.x = d.y;
sphere.position.y = 0; //should be 0!
sphere.position.z = d.y;
objects.push(sphere);
pivot.add(sphere);
});
nodesLength = y;
render();
$('.loading').fadeOut(500);
if (!animationId) {
animate();
}
temp = null;
nodes = null;
}
So I'm adding spheres to parent Object3D and then add it to scene.
And here is destroy function:
function destroyTree() {
//spheres
//console.log(renderer.info);
var to_delete = [];
for (var i=0; i<spheres.length; i++) {
scene.remove(spheres[i]);
spheres[i].material.dispose();
spheres[i].geometry.dispose();
}
for (var i=0; i<spheres.length; i++) {
spheres[i] = undefined;
}
spheres = {};
for (var i=0; i<objects.length; i++) {
scene.remove(objects[i]);
}
for (var i=0; i<objects.length; i++) {
objects[i] = undefined;
}
objects = [];
var parent = scene.getObjectByName('parent');
scene.remove(parent);
if (links.length) {
for (var i=0; i<links.length; i++) {
scene.remove(links[i]);
}
}
links = [];
scene.traverse(function (child) {
if (child instanceof THREE.Mesh) {
if (child.name.length) {
to_delete.push(child);
}
}
});
for (var i=0; i<to_delete.length; i++) {
scene.remove(to_delete[i]);
to_delete[i].geometry.dispose();
to_delete[i].material.dispose();
to_delete[i] = undefined;
}
to_delete = [];
}
Wouldn't traversing the scene find the spheres too? And in that loop you could dispose directly without need for the to_delete array. If all speres are not children of the scene then maybe reconsider when to create them? These would just be optimizations and probably do little other then clarify where it might be leaking.
Then again maybe try holding an array of textures and releasing those directly?
Wait here it is, this link says to remove the objects and textures from the renderer as well.
Memory leak in Three.js
renderer.deallocateObject
renderer.deallocateTexture

JavaScript create objects in for-loop

I'm making a canvas game in JavaScript and have some trouble saving the data. I'm placing images on the canvas with a for-loop and I want to save information for each image in objects. For each image an object.
function CreateBlocks(){
for(var i = 0; i <= blocks; i++){
var img = new Image();
img.src = "/images/Block.png";
blockObject = {
x: x,
y: y,
points: 10
}
ctx.drawImage(img,x,y);
x += 100;
y += 100;
}
}
Now this obviously overwrites the blockObject everytime it loops. I tried adding to loop value to the name of the object like block[i]Object or blockObject[i] but that returns syntax errors.
I could just create a single dimension array for each value, but that seems rather messy to me. How can I create the objects in the loop?
Simply use an array and push the new object each time:
function CreateBlocks(){
var arr = [];
for(var i = 0; i <= blocks; i++){
var img = new Image();
img.src = "/images/Block.png";
arr.push({
x: x,
y: y,
points: 10
});
ctx.drawImage(img,x,y);
x += 100;
y += 100;
}
}
If you create a blockObjects array, your second idea, using the blockObject[i] syntax will work:
var blockObjects=[];
function CreateBlocks(){
for(var i = 0; i <= blocks; i++){
var img = new Image();
img.src = "/images/Block.png";
blockObjects[i] = {
x: x,
y: y,
points: 10
};
ctx.drawImage(img,x,y);
x += 100;
y += 100;
}
}

javascript - iterating over bidimensional arrays

I am trying to write a javascript program that renders an 8x8 grid of dirt tiles on an HTML5 canvas. However, when I run this code it throws up error messages when running the draw_terrain() function and it appears to be a problem with the blockArray.length component. Can someone explain to me how to fix this problem? Thanks in advance.
//Define initial canvas variables and images
var canvas;
var ctx;
var WIDTH = 800;
var HEIGHT = 800;
var dirt = new Image();
dirt.src = 'dirt.png';
//Function called to initialise canvas variables and run draw on interval
function init(){
canvas = document.getElementById('myCanvas');
ctx = canvas.getContext('2d');
return setInterval(draw, 15);
}
//Function that generates an 8x8 Array called blockArray
function gen_terrain(){
var blockArray = new Array(8);
for(var i=0; i<blockArray.length; i++) {
blockArray[i] = new Array(8);
for(var j=0; j<blockArray[i].length; j++){
blockArray[i][j] = 0;
};
};
}
//Function that returns a random number between a min and max
function randomRange (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function draw_terrain(){
for(var i=0; i<blockArray.length; i++) {
for(var j=0; j<blockArray[i].length; j++){
ctx.drawImage(dirt,(n-1)*32,(j-1)*32);
};
};
}
function draw(){
draw_terrain();
}
gen_terrain();
init();
Your problem, as other people have explained is that the variable you are using the build the array will not exist by the time the draw occurs. Just place your array declaration outside of the function and your issue will go away.
See comment below:
function init(){
canvas = document.getElementById('myCanvas');
ctx = canvas.getContext('2d');
return setInterval(draw, 15);
}
//Function that generates an 8x8 Array called blockArray
var blockArray = []; // <== has to be global (outside function).
function gen_terrain(){
// Not here -> var blockArray = [];
for(var i=8; i--;) {
blockArray[i] = [0,0,0,0,0,0,0,0];
};
}
Example

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