Draw an image on a Canvas from an Object property - javascript

I want to draw an image on the canvas at each iteration, an image coming from an object.
Either I get "not the format HTML..." in the console, or nothing and it blocks the loop.
Is there a way to draw an image on a canvas without putting it first on the index.html, or without loading from an URL?
I tried the two standard solutions and they didn't work.
I didn't find a similar problem using an object property containing the image, to draw it on a canvas.
function Board(width, height) {
this.width = width;
this.height = height;
this.chartBoard = [];
// Création du plateau logique
for (var i = 0; i < this.width; i++) {
const row = [];
this.chartBoard.push(row);
for (var j = 0; j < this.height; j++) {
const col = {};
row.push(col);
}
}
}
let board = new Board(10, 10);
console.log(board);
// CONTEXT OF THE CANVAS
const ctx = $('#board').get(0).getContext('2d');
Board.prototype.drawBoard = function () {
for (var i = 0; i < this.width; i++) {
for (var j = 0; j < this.height; j++) {
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.strokeRect(j * 64, i * 64, 64, 64);
ctx.closePath();
}
}
};
board.drawBoard();
Board.prototype.test = test;
function test() {
console.log(this);
}
// OBJECT TO DRAW
function Obstacle(name, sprite) {
this.name = name;
this.sprite = sprite;
}
const lava = new Obstacle("Lave", "assets/lave.png");
const lava1 = new Obstacle("Lave1", "assets/lave.png");
const lava2 = new Obstacle("Lave2", "assets/lave.png");
const lava3 = new Obstacle("Lave3", "assets/lave.png");
const lava4 = new Obstacle("Lave4", "assets/lave.png");
const lava5 = new Obstacle("Lave5", "assets/lave.png");
const lava6 = new Obstacle("Lave6", "assets/lave.png");
const lava7 = new Obstacle("Lave7", "assets/lave.png");
const lava8 = new Obstacle("Lave8", "assets/lave.png");
const lava9 = new Obstacle("Lave9", "assets/lave.png");
const lavaArray = [lava, lava1, lava2, lava3, lava4, lava5, lava6, lava7, lava8, lava9];
// FUNCTION TO DRAW
Board.prototype.setPiece = function (piece) {
let randomX = Math.floor(Math.random() * board.width);
let randomY = Math.floor(Math.random() * board.height);
let drawX = randomX * 64;
let drawY = randomY * 64;
if (randomX >= this.width || randomY >= this.height) {
throw new Error('Pièce hors limite');
}
if (piece instanceof Obstacle) {
if (!(this.chartBoard[randomY][randomX] instanceof Obstacle)) {
this.chartBoard[randomY][randomX] = piece;
// CODE TO DRAW, BUG DOESN'T WORK
ctx.fillRect(drawX, drawY,64,64);
let image = Obstacle.sprite;
ctx.drawImage = (image, drawX, drawY);
}
}
} else {
throw new Error('Pièce non valide');
}
};
Board.prototype.setObstacles = function () {
for (let lava of lavaArray) {
const obstacle = board.setPiece(lava);
}
};
board.setObstacles();
Actual: No image is drawn. And if I try fillRect, it works well. So the loop works.
Expected: Be able to draw an image on a canvas from an object property.

It's not really clear what you're trying to do.
The code you have commented
ctx.drawImage = (image, drawX, drawY);
should be this
ctx.drawImage(image, drawX, drawY);
Looking a little broader you have this
let image = Obstacle.sprite;
ctx.drawImage(image, drawX, drawY); // assume this was fixed
But Obstacle is a class, not an instance of that class. You want
let image = piece.sprite;
ctx.drawImage(image, drawX, drawY);
But that leads the next issue. looking at the rest of the code piece.sprite is a string not an image. See this code.
// OBJECT TO DRAW
function Obstacle(name, sprite) {
this.name = name;
this.sprite = sprite;
}
const lava = new Obstacle("Lave", "assets/lave.png");
There are a several ways you can draw images to a canvas. If they come from a file you do have to wait for them to download. Otherwise you can generate them from another canvas. You can also use createImageData and putImageData as another way to make images.
Let's change the code to load a bunch of images and then start
I moved all the class code to the top and the start up code to the bottom.
There were a few places inside Board methods where the global variable board was used instead of this so I fixed those.
Here`s a function that loads an image and returns a Promise
function loadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => { resolve(img); };
img.onerror = reject;
img.crossOrigin = 'anonymous'; // REMOVE IF SAME DOMAIN!
img.src = url;
});
}
You could use that to load one image like this
loadImage(urlOfImage).then(function(img) {
// use the loaded img
});
I used that function to write this function that takes an object of names to urls and returns an object of names to loaded images.
function loadImages(images) {
return new Promise((resolve) => {
const loadedImages = {};
const imagePromises = Object.entries(images).map((keyValue) => {
const [name, url] = keyValue;
return loadImage(url).then((img) => {
loadedImages[name] = img;
});
});
Promise.all(imagePromises).then(() => {
resolve(loadedImages);
});
});
}
I then call that and pass the object of names to loaded images to the start function. Only one image is loaded at the moment but you can add more.
const images = {
lave: 'https://i.imgur.com/enx5Xc8.png',
// player: 'foo/player.png',
// enemy: 'foo/enemny.png',
};
loadImages(images).then(start);
// CONTEXT OF THE CANVAS
const ctx = $('#board').get(0).getContext('2d');
function Board(width, height) {
this.width = width;
this.height = height;
this.chartBoard = [];
// Création du plateau logique
for (var i = 0; i < this.width; i++) {
const row = [];
this.chartBoard.push(row);
for (var j = 0; j < this.height; j++) {
const col = {};
row.push(col);
}
}
}
Board.prototype.drawBoard = function () {
for (var i = 0; i < this.width; i++) {
for (var j = 0; j < this.height; j++) {
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.strokeRect(j * 64, i * 64, 64, 64);
ctx.closePath();
}
}
};
// OBJECT TO DRAW
function Obstacle(name, sprite) {
this.name = name;
this.sprite = sprite;
}
// FUNCTION TO DRAW
Board.prototype.setPiece = function (piece) {
let randomX = Math.floor(Math.random() * this.width);
let randomY = Math.floor(Math.random() * this.height);
let drawX = randomX * 64;
let drawY = randomY * 64;
if (randomX >= this.width || randomY >= this.height) {
throw new Error('Pièce hors limite');
}
if (piece instanceof Obstacle) {
if (!(this.chartBoard[randomY][randomX] instanceof Obstacle)) {
this.chartBoard[randomY][randomX] = piece;
// CODE TO DRAW, BUG DOESN'T WORK
ctx.fillRect(drawX, drawY,64,64);
let image = piece.sprite;
ctx.drawImage(image, drawX, drawY);
}
} else {
throw new Error('Pièce non valide');
}
};
Board.prototype.setObstacles = function (lavaArray) {
for (let lava of lavaArray) {
const obstacle = this.setPiece(lava);
}
};
function start(images) {
let board = new Board(10, 10);
// console.log(board);
const lava = new Obstacle("Lave", images.lave);
const lava1 = new Obstacle("Lave1", images.lave);
const lava2 = new Obstacle("Lave2", images.lave);
const lava3 = new Obstacle("Lave3", images.lave);
const lava4 = new Obstacle("Lave4", images.lave);
const lava5 = new Obstacle("Lave5", images.lave);
const lava6 = new Obstacle("Lave6", images.lave);
const lava7 = new Obstacle("Lave7", images.lave);
const lava8 = new Obstacle("Lave8", images.lave);
const lava9 = new Obstacle("Lave9", images.lave);
const lavaArray = [lava, lava1, lava2, lava3, lava4, lava5, lava6, lava7, lava8, lava9];
board.drawBoard();
board.setObstacles(lavaArray);
}
function loadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => { resolve(img); };
img.onerror = reject;
img.crossOrigin = 'anonymous'; // REMOVE IF SAME DOMAIN!
img.src = url;
});
}
function loadImages(images) {
return new Promise((resolve) => {
const loadedImages = {};
// for each name/url pair in image make a promise to load the image
// by calling loadImage
const imagePromises = Object.entries(images).map((keyValue) => {
const [name, url] = keyValue;
// load the image and when it's finished loading add the name/image
// pair to loadedImages
return loadImage(url).then((img) => {
loadedImages[name] = img;
});
});
// wait for all the images to load then pass the name/image object
Promise.all(imagePromises).then(() => {
resolve(loadedImages);
});
});
}
const images = {
lave: 'https://i.imgur.com/enx5Xc8.png',
// player: 'foo/player.png',
// enemy: 'foo/enemny.png',
};
loadImages(images).then(start);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="board" width="640" height="640"></canvas>

Related

How to fix "TypeError: Failed to execute 'drawImage' on 'CanvasRenderingContext2D'"?

I am a beginner JavaScript programmer and I am following a book called "Create with < code >" and I am having trouble with the code. I have been following along with the book but when I have tried running it, I get this error:
Uncaught TypeError: Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The provided value is not of type '(CSSImageValue or HTMLImageElement or SVGImageElement or HTMLVideoElement or HTMLCanvasElement or ImageBitmap or OffscreenCanvas)
I have been typing the code into a plain-text editor and viewing it on Chrome. How do I fix this error?
If you run the snippet you should be able to see the error.
//CONSTANTS
var CANVAS_WIDTH = 800;
var CANVAS_HEIGHT = 600;
var GROUND_Y = 540;
//SETUP
var canvas = document.createElement('canvas');
var c = canvas.getContext('2d');
canvas.width = CANVAS_WIDTH;
canvas.height = CANVAS_HEIGHT;
document.body.appendChild(canvas);
var cameraX = 0;
var bushData = generateBushes();
var bush1Image = new Image();
bush1Image.src = 'bush1.png';
var bush2Image = new Image();
bush2Image.src = 'bush2.png';
window.addEventListener('load', start);
function start() {
window.requestAnimationFrame(mainLoop);
}
function generateBushes() {
var generatedBushData = [];
var bushX = 0;
while (bushX < (2 * CANVAS_WIDTH)) {
var bushImage;
if (Math.random() >= 0.5) {
bushImage = bush1Image;
} else {
bushImage = bush2Image
}
generatedBushData.push({
x: bushX,
y: 80 + Math.random() * 20,
image: bushImage
});
bushX += 150 + Math.random() * 200;
}
return generatedBushData;
}
//MAIN LOOP
function mainLoop() {
update();
draw();
window.requestAnimationFrame(mainLoop);
}
//UPDATING
function update() {
//Update bushes.
for (var i = 0; i < bushData.length; i++) {
if ((bushData[i].x - cameraX) < -CANVAS_WIDTH) {
bushData[i].x += (2 * CANVAS_WIDTH) + 150;
}
}
}
//DRAWING
function draw() {
//Draw the bushes
for (var i = 0; i < bushData.length; i++) {
c.drawImage(
bushData[i].image,
bushData[i].x,
GROUND_Y - bushData[i].y);
}
}
DanieleAlessandra put his finger on the error in a comment: image in the objects in bushData is undefined, because you're calling generateBushes too soon. You have:
var bushData = generateBushes();
var bush1Image = new Image();
bush1Image.src = 'bush1.png';
var bush2Image = new Image();
bush2Image.src = 'bush2.png';
but you need to initialize bush1Image and bush2Image before calling generateBushes. Just moving var bushData = generateBushes(); to after bush2Image.src = 'bush2.png'; fixes it.

Prototypal Inheritance in Javascript Working with Basic but not Complex Program

What is confusing is how this simple script works fine:
function A() {
this.value = 0;
}
A.prototype.foo = function() {
console.log(this.value);
};
function B() {
this.value = 1;
this.foo();
}
B.prototype = Object.create(A.prototype);
B.prototype.bar = function() {
console.log(this instanceof A);
}
new B().bar();
// outputs 1, true
However, this larger script gives an error this.loadDimensions is not a function:
Basically, there is a Player class, which inherits from a MovingComponent class, which inherits from a VisibleComponent class. They all have methods attached to them.
const PX_SZ = 4, MAX_HEIGHT = 100, MIN_HEIGHT = 300;
var resources = {};
resources.sprites = {};
resources.sprites.player = new Image();
resources.sprites.player.src = "resources/sprites/player.png";
resources.sprites['default'] = new Image();
resources.sprites['default'].src = "resources/sprites/default.png";
resources.sprites.items = {};
resources.sprites.backgroundEntities = {};
var itemsTemp = ['default', 'coin0'];
for (var i=0; i<itemsTemp.length; i++) {
var item = itemsTemp[i];
resources.sprites.items[item] = new Image();
resources.sprites.items[item].src = "resources/sprites/items/" + item + ".png";
}
var backgroundEntitiesTemp = ['tree0'];
for (var i=0; i<backgroundEntitiesTemp.length; i++) {
var ent = backgroundEntitiesTemp[i];
resources.sprites.backgroundEntities[ent] = new Image();
resources.sprites.backgroundEntities[ent].src = "resources/sprites/background-entities/" + ent + ".png";
}
var canvas, ctx;
var player = new Player();
var keys = {};
var game = new Game(Math.floor(Math.random()*1000000));
var world = new World();
/** #class */
function Game(seed) {
this.seed = seed;
}
/** #class */
function World() {
this.gravity = 0.4;
this.chances = {
items: {
coin0: 0.005
},
backgroundEntities: {
tree0: 0.05
}
};
this.itemsFloating = [];
this.backgroundEntities = [];
// for spawning
this.exploredRightBound = 0;
this.exploredLeftBound = 0;
}
World.prototype.generate = function(left, right) {
if (left >= right) throw "left >= right in World#generate(left,right)";
for (x = left; x < right; x += PX_SZ) {
// world generation code here
// coin0
var level = getGroundHeightAt(x)
if (Math.random() <= this.chances.items.coin0) {
var item = new ItemFloating("coin0", x, level-20);
this.itemsFloating.push(item);
}
if (Math.random() <= this.chances.backgroundEntities.tree0) {
var ent = new BackgroundEntity("tree0", x, level-resources.sprites.backgroundEntities.tree0.height);
this.backgroundEntities.push(ent);
}
}
};
/**
* #class
* anything that has a sprite attached to it
*/
function VisibleComponent() {
this.sprite = resources.sprites['default'];
}
VisibleComponent.prototype.loadDimensions = function() {
console.log('load');
};
VisibleComponent.prototype.draw = function() {
ctx.drawImage(this.sprite, this.x, this.y, this.width, this.height);
};
/** #class */
function Item(name="default") {
VisibleComponent.call(this);
this.name = name || "default";
this.sprite = resources.sprites.items[name];
this.loadDimensions();
}
Item.prototype = Object.create(VisibleComponent.prototype);
/** #class */
function ItemFloating(name, x, y) {
Item.call(this, name);
this.name = name;
this.x = x;
this.y = y;
this.loadDimensions(); // (when ready of now)
}
ItemFloating.prototype = Object.create(Item.prototype);
/** #class */
function BackgroundEntity(name="default", x=0, y=0) {
VisibleComponent.call(this);
this.name = name;
this.x = x;
this.y = y;
this.width = 1;
this.height = 1;
this.sprite = resources.sprites.backgroundEntities[this.name];
this.loadDimensions();
}
BackgroundEntity.prototype = Object.create(VisibleComponent.prototype);
/** #class */
function MovingEntity(x=0, y=0) {
VisibleComponent.call(this);
this.x = x;
this.y = y;
this.width = 1;
this.height = 1;
}
MovingEntity.prototype = Object.create(VisibleComponent.prototype);
MovingEntity.prototype.collisionWith = function(ent) {
return ((this.x>=ent.x&&this.x<=ent.x+ent.width) || (ent.x>=this.x&&ent.x<=this.x+this.width))
&& ((this.y>=ent.y&&this.y<=ent.y+ent.height) || (ent.y>=this.y&&ent.y<=this.y+this.height));
};
/** #class */
function Player() {
MovingEntity.call(this);
this.inventory = {};
console.log(this instanceof VisibleComponent);
this.speed = 4;
this.jumpSpeed = 8;
this.vspeed = 0;
this.sprite = resources.sprites.player;
this.loadDimensions();
this.direction = "right";
}
Player.prototype = Object.create(MovingEntity.prototype);
Player.prototype.draw = function() {
ctx.save();
ctx.translate(this.x, this.y);
if (this.direction == "left") ctx.scale(-1, 1); // flip over y-axis
ctx.translate(-this.sprite.width, 0);
ctx.drawImage(this.sprite, 0, 0, this.width, this.height);
ctx.restore();
}
Player.prototype.move = function() {
if (keys['ArrowLeft']) {
this.x -= this.speed;
this.direction = "left";
var leftEdge = this.x-canvas.width/2-this.width/2;
if (leftEdge < world.exploredLeftBound) {
world.generate(leftEdge, world.exploredLeftBound);
world.exploredLeftBound = leftEdge;
}
}
if (keys['ArrowRight']) {
this.x += this.speed;
this.direction = "right";
var rightEdge = this.x+canvas.width/2+this.width/2;
if (rightEdge > world.exploredRightBound) {
world.generate(world.exploredRightBound, rightEdge);
world.exploredRightBound = rightEdge;
}
}
var level = getGroundHeightAt(this.x+this.width/2);
if (this.y + this.height < level) {
this.vspeed -= world.gravity;
} else if (this.y + this.height > level) {
this.y = level - this.height;
this.vspeed = 0;
}
if (keys[' '] && this.y+this.height == getGroundHeightAt(this.x+this.width/2)) this.vspeed += this.jumpSpeed;
this.y -= this.vspeed;
for (var i=0; i<world.itemsFloating.length; i++) {
var item = world.itemsFloating[i];
if (this.collisionWith(item)) {
if (this.inventory.hasOwnProperty(item.name)) this.inventory[item.name]++;
else this.inventory[item.name] = 1;
world.itemsFloating.splice(i, 1);
}
}
};
I'm fairly new to javascript inheritance, so I don't understand what I'm doing wrong. Also, since the first script worked, I figured there's something I'm just overlooking in my second script. Any help would be appreciated.
EDIT
In the beginning of the file, I declare player as a new Player(). resources contains Image instances that point to various image files. ctx and canvas are pretty self-explanatory globals.
Also, player isn't recognized as an instance of MovingEntity or VisibleComponent, even though Player's prototype is set to Object.create(MovingEntity.prototype), which has its prototype set to Object.create(VisibleComponent.prototype).
One other thing to mention is that in the definition of loadDimensions() in VisibleComponent, either the onload property of this.sprite is set to a function, or addEventListener() is called for 'load', depending on whether this.sprite has loaded (width != 0) or not.
In the beginning of the file, I declare player as a new Player().
That's the problem, you need to call the constructor after having set up your class. It currently doesn't throw an error about Player not being a function because the declaration is hoisted, but the prototype is not yet initialised with the value you expect so it indeed does not have a .loadDimensions() method yet.

Why is this neural network not rendering (only) full 3-layer pathways?

The function in question is rendering the neural network represented on the right side of this image based on a fairly simple data structure.
Trying to evolve:
After evolving:
Each dot represents a neuron, each line a connection. There's a problem with which neurons and connections are being rendered and which aren't, and I've been wrestling with this problem for 5 hours straight, no coffee breaks. One of you is likely going to point out one tiny stupid mistake causing the issue and I'll likely proceed to pull my hair out.
Put simply: Connections run from the top down. I only want to render complete paths from top to bottom. No dots (neurons) in the middle should be rendered if no connections lead to them from the top. If a top layer neuron connects to a middle layer neuron but that middle layer neuron doesn't connect to a bottom layer neuron, that connection isn't doing anything so it shouldn't be rendered.
As you can see, there are top level neurons that are rendered with no connections at all, and middle level neurons which are rendered with no connection to the top. All connections are top-down, no connections flow upward. The network is feed-forward in other words.
The data structure passed to this function is brain which is the same brain passed to the following Neuron and Connection constructors which are listed in abridged form only showing the properties relevant to the function in question (edited from the original with further attempts to fix the problem:
Neuron
function Neuron(brain, layer) {
var that = this;
brain.counter++;
brain.globalReferenceNeurons[brain.counter] = this;
this.active = true; //as the brain mutates, some neurons and
//connections are disabled via this property
this.layer = layer;
this.id = brain.counter;
this.connected = {};
this.connections = {};
this.connect = function(target) {
if (that.active == true) {
new Connection(brain, this, target, function(id, connection) {
brain.globalReferenceConnections[id] = connection;
that.connections[id] = connection;
});
}
};
}
Connection
function Connection(brain, source, target, callback) {
if (source.layer < target.layer) {
brain.counter++;
brain.globalReferenceConnections[brain.counter] = this;
this.active = true; //as the brain mutates, some neurons and
//connections are disabled via this property
this.id = brain.counter;
this.source = source;
this.target = target;
target.connected[this.id] = this; //connected references
//incoming connections to a neuron
callback(this.id, this);
}
}
As you can see, brain.globalReferenceNeurons contains the the data needed to render the neural network in the picture.
And here's the rendering function in question (updated again):
function renderBrain(brain, context, canvas) {
context.clearRect(0, 0, canvas.width, canvas.height);
var width = canvas.width;
var height = canvas.height;
var layers = brain.layers;
var heightDivision = height / layers;
var layerList = [];
for (var i1 = 0; i1 < brain.layers; i1++) {
layerList.push([]);
for (var prop1 in brain.globalReferenceNeurons) {
if (brain.globalReferenceNeurons[prop1].layer === i1) {
layerList[i1].push(brain.globalReferenceNeurons[prop1]);
}
}
}
function renderLayer(layer, layerCount, layerTotal) {
var length = layer.length;
var widthDivision = width / length;
var neuronCount = 0;
for (var i1 = 0; i1 < layer.length; i1++) {
neuronCount++;
const getActiveProps = obj => Object.keys(obj).filter(k => obj[k].active)
function hasActivePathAhead(obj, count) {
if (!count) {
count = 0;
}
if (obj.active) {
var targets = getActiveProps(obj.connections);
if (obj.layer === 2) {
return true;
} else if (obj.connections[targets[count]]) {
for (var i1 = 0; i1 < targets.length; i1++) {
var result = hasActivePathAhead(obj.connections[targets[count]].target,
count + 1);
return result;
}
return false;
} else {
return false;
}
} else {
return false;
}
}
function hasActivePathBehind(obj, count) {
if (!count) {
count = 0;
}
if (obj.active) {
var sources = getActiveProps(obj.connected);
if (obj.layer === 0) {
return true;
} else if (obj.connected[sources[count]]) {
for (var i1 = 0; i1 < sources.length; i1++) {
var result =
hasActivePathBehind(obj.connected[sources[count]].source, count + 1);
return result;
}
return false;
} else {
return false;
}
} else {
return false;
}
}
if (hasActivePathAhead(layer[i1]) && hasActivePathBehind(layer[i1])) {
context.beginPath();
context.arc((widthDivision * neuronCount)
- (0.5 * widthDivision),
(heightDivision * layerCount)
- (heightDivision * 0.5),
5, 0, 2 * Math.PI, false);
context.fillStyle = '#adf442';
context.fill();
context.lineWidth = 2;
context.strokeStyle = '#56cc41';
context.stroke();
var connectionCount = 0;
for (var i2 = 0; i2 < Object.keys(layer[i1].connections).length; i2++) {
var connection =
layer[i1].connections[Object.keys(layer[i1].connections)[i2]];
if (hasActivePathAhead(connection.target)
&& hasActivePathBehind(connection.target)) {
var targetLayer = connection.target.layer;
var index = layerList[targetLayer].findIndex(function(e) {
return e == connection.target
});
if (index > -1) {
var targetLayerLength = Object.keys(layerList[targetLayer]).length;
var targetLayerWidthDivision = width / targetLayerLength;
var p1 = {
x: (widthDivision * neuronCount) - (0.5 * widthDivision),
y: (heightDivision * layerCount) - (heightDivision * 0.5)
};
var p2 = {
x: (index * targetLayerWidthDivision)
+ (0.5 * targetLayerWidthDivision),
y: (targetLayer * heightDivision)
+ (heightDivision * 0.5)
};
connectionCount++;
context.beginPath();
context.moveTo(p1.x, p1.y);
context.lineTo(p2.x, p2.y);
context.lineWidth = 1;
context.stroke();
}
}
}
}
}
}
var layerCount = 0;
for (i1 = 0; i1 < layerList.length; i1++) {
layerCount++;
renderLayer(layerList[i1], layerCount, layerList.length);
}
}
"Working" test example: https://jsfiddle.net/au2Lt6na/4/
For 5 hours I've tinkered with this function trying to pinpoint the issue and for the life of me I haven't been able to figure it out. Can anyone tell me what's causing the rendering of non-top-to-bottom neural pathways?
Note: I've spent many more hours over the past few days trying to fix this, writing totally new ways of figuring out which paths are complete from top to bottom and it still suffers from the same issues as before. I'm missing something here.
for (var i1 = 0; i1 < targets.length; i1++) {
var result = hasActivePathAhead(obj.connections[targets[count]].target,
count + 1);
return result;
}
This snippet is weird. You may need this instead:
for (var i1 = 0; i1 < targets.length; i1++) {
var result = hasActivePathAhead(obj.connections[targets[count]].target,
count + 1);
if(result){
return true;
}
}
And the usage of count is weird there. I think the count should not be passed as a param, since it's used to index a target or source.
I think the snippet should be like this:
else if (targets.length) {
var target;
for (var i1 = 0; i1 < targets.length; i1++) {
target=targets[i1];
var result = hasActivePathAhead(obj.connections[target].target);
if(result){
return true;
}
}
return false;
}
Live demo here:
function Neuron(brain, layer) {
var that = this;
brain.counter++;
brain.globalReferenceNeurons[brain.counter] = this;
this.active = true; //as the brain mutates, some neurons and
//connections are disabled via this property
this.layer = layer;
this.id = brain.counter;
this.connected = {};
this.connections = {};
this.connect = function (target) {
if (that.active == true) {
new Connection(brain, this, target, function (id, connection) {
brain.globalReferenceConnections[id] = connection;
that.connections[id] = connection;
});
}
};
}
function Connection(brain, source, target, callback) {
if (source.layer < target.layer) {
brain.counter++;
brain.globalReferenceConnections[brain.counter] = this;
this.active = true; //as the brain mutates, some neurons and
//connections are disabled via this property
this.id = brain.counter;
this.source = source;
this.target = target;
target.connected[this.id] = this;
callback(this.id, this);
}
}
function renderBrain(brain, context, canvas) {
context.clearRect(0, 0, canvas.width, canvas.height);
var width = canvas.width;
var height = canvas.height;
var layers = brain.layers;
var heightDivision = height / layers;
var layerList = [];
for (var i1 = 0; i1 < brain.layers; i1++) {
layerList.push([]);
for (var prop1 in brain.globalReferenceNeurons) {
if (brain.globalReferenceNeurons[prop1].layer === i1) {
layerList[i1].push(brain.globalReferenceNeurons[prop1]);
}
}
}
function renderLayer(layer, layerCount, layerTotal) {
var length = layer.length;
var widthDivision = width / length;
var neuronCount = 0;
for (var i1 = 0; i1 < layer.length; i1++) {
neuronCount++;
const getActiveProps = obj => Object.keys(obj).filter(k => obj[k].active)
function hasActivePathAhead(obj) {
if (obj.active) {
var targets = getActiveProps(obj.connections);
if (obj.layer === 2) {
return true;
} else if (targets.length) {
var target;
for (var i1 = 0; i1 < targets.length; i1++) {
target = targets[i1];
var result = hasActivePathAhead(obj.connections[target].target);
if (result) {
return true;
}
}
return false;
} else {
return false;
}
} else {
return false;
}
}
function hasActivePathBehind(obj) {
if (obj.active) {
var sources = getActiveProps(obj.connected);
if (obj.layer === 0) {
return true;
} else if (sources.length) {
var source;
for (var i1 = 0; i1 < sources.length; i1++) {
source = sources[i1];
var result =
hasActivePathBehind(obj.connected[source].source);
return result;
}
return false;
} else {
return false;
}
} else {
return false;
}
}
if (hasActivePathAhead(layer[i1]) && hasActivePathBehind(layer[i1])) {
context.beginPath();
context.arc((widthDivision * neuronCount) -
(0.5 * widthDivision),
(heightDivision * layerCount) -
(heightDivision * 0.5),
5, 0, 2 * Math.PI, false);
context.fillStyle = '#adf442';
context.fill();
context.lineWidth = 2;
context.strokeStyle = '#56cc41';
context.stroke();
var connectionCount = 0;
for (var i2 = 0; i2 < Object.keys(layer[i1].connections).length; i2++) {
var connection =
layer[i1].connections[Object.keys(layer[i1].connections)[i2]];
if (hasActivePathAhead(connection.target) &&
hasActivePathBehind(connection.target)) {
var targetLayer = connection.target.layer;
var index = layerList[targetLayer].findIndex(function (e) {
return e == connection.target
});
if (index > -1) {
var targetLayerLength = Object.keys(layerList[targetLayer]).length;
var targetLayerWidthDivision = width / targetLayerLength;
var p1 = {
x: (widthDivision * neuronCount) - (0.5 * widthDivision),
y: (heightDivision * layerCount) - (heightDivision * 0.5)
};
var p2 = {
x: (index * targetLayerWidthDivision) +
(0.5 * targetLayerWidthDivision),
y: (targetLayer * heightDivision) +
(heightDivision * 0.5)
};
connectionCount++;
context.beginPath();
context.moveTo(p1.x, p1.y);
context.lineTo(p2.x, p2.y);
context.lineWidth = 1;
context.stroke();
}
}
}
}
}
}
var layerCount = 0;
for (i1 = 0; i1 < layerList.length; i1++) {
layerCount++;
renderLayer(layerList[i1], layerCount, layerList.length);
}
}
var brain = {
counter: 0,
layers: 3,
globalReferenceNeurons: {},
globalReferenceConnections: {},
}
var layer0 = [new Neuron(brain, 0), new Neuron(brain, 0), new Neuron(brain, 0),new Neuron(brain, 0), new Neuron(brain, 0)];
var layer1 = [new Neuron(brain, 1), new Neuron(brain, 1), new Neuron(brain, 1)];
var layer2 = [new Neuron(brain, 2), new Neuron(brain, 2), new Neuron(brain, 2), new Neuron(brain, 2)];
layer0[0].connect(layer1[1]);
layer0[1].connect(layer1[0]);
layer0[3].connect(layer1[0]);
layer1[0].connect(layer2[0]);
layer1[2].connect(layer2[2]);
layer1[1].connect(layer2[3]);
var canvas = document.getElementById('cav');
var ctx = canvas.getContext('2d');
renderBrain(brain, ctx, canvas);
<canvas id="cav" width="600" height="400"></canvas>
Could not fit into the comment and as you do not have a working example we can only guess.
Your recursion does not look right to me. The count variable makes no sense and you have several levels of redundancy checking for active 3 times for each iteration and not vetting when indexing into the key arrays with count.
Without working code and as your variable nomenclature is confusing this is only a guess at how to fix. Same applies to hasActivePathBehind
Ignore the following code
function hasActivePathAhead(obj) {
if (obj.active) {
if (obj.layer === 2) {
return true;
}
var targets = getActiveProps(obj.connections);
for (var i = 0; i < targets.length; i++) {
if(hasActivePathAhead(obj.connections[targets[i]].target)){
return true;
}
}
}
return false;
}
UPDATE and working fix.
Update because accepted answer does not work, it should have used a more rigorous test.
As you have provided a fiddle in the comments I had a look as see that you have removed the count from the functions. Though the functions are still incorrect and not working, the error is elsewhere in the code. The code is overly complex hiding the nature of the bug.
There is too much wrong to go into detail so I have just started from scratch.
Rather than test every node for a backward and forward link I traverse the layers forward from top to bottom. This negates the need to check backward connections. I have a function that checks if a node is connected to the bottom, a function that draws all nodes from a node to the bottom, and a function that draws all active nodes at a layer (active is connected from that layer down)
You can optimise it by adding a flag to nodes indicating that they have already been rendered as the code as is can render some nodes several times. But I did not add that as I did not want to modify the data structure you had. Or you can add a Map that holds node pairs that have been rendered and check that to see if a node pair needs to be rendered.
Using your fiddle as a template here is a working version using the randomised paths as provided in the fiddle.
function Neuron(brain, layer) {
var that = this;
brain.counter++;
brain.globalReferenceNeurons[brain.counter] = this;
this.active = true; //as the brain mutates, some neurons and
//connections are disabled via this property
this.layer = layer;
this.id = brain.counter;
this.connected = {};
this.connections = {};
this.connect = function (target) {
if (that.active == true) {
new Connection(brain, this, target, function (id, connection) {
brain.globalReferenceConnections[id] = connection;
that.connections[id] = connection;
});
}
};
}
function Connection(brain, source, target, callback) {
if (source.layer < target.layer) {
brain.counter++;
brain.globalReferenceConnections[brain.counter] = this;
this.active = true; //as the brain mutates, some neurons and
//connections are disabled via this property
this.id = brain.counter;
this.source = source;
this.target = target;
target.connected[this.id] = this;
callback(this.id, this);
}
}
function renderBrain(brain, ctx, canvas) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
var width = canvas.width;
var height = canvas.height;
var layers = brain.layers;
var heightDiv = height / layers;
var layerList = [];
for (var i1 = 0; i1 < brain.layers; i1++) {
layerList.push([]);
for (var prop1 in brain.globalReferenceNeurons) {
if (brain.globalReferenceNeurons[prop1].layer === i1) {
layerList[i1].push(brain.globalReferenceNeurons[prop1]);
}
}
}
var coord; // to hold node coordinates defined here to prevent pointless memory allocation dealocation cycle
// Gets the node position based on its ID and layer position
function nodePosition(node,coord = {}){
var pos;
pos = node.id - layerList[node.layer][0].id; // get pos from node id (if this does not hold true you should include the node position in the node data is it is important)
coord.x = (width / layerList[node.layer].length) * (pos + 0.5);
coord.y = heightDiv * (node.layer + 0.5);
return coord;
}
// draws a node
function drawNode(node){
ctx.strokeStyle = '#56cc41';
ctx.fillStyle = '#adf442';
ctx.lineWidth = 2;
coord = nodePosition(node,coord);
ctx.beginPath();
ctx.arc(coord.x,coord.y, 5, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();
}
// draws a link between two nodes
function drawLink(node,node1){
ctx.strokeStyle = '#56cc41';
ctx.lineWidth = 1;
coord = nodePosition(node,coord);
ctx.beginPath();
ctx.moveTo(coord.x,coord.y);
coord = nodePosition(node1,coord);
ctx.lineTo(coord.x,coord.y);
ctx.stroke();
}
// returns true if the path from this node jas a connection that leads to the end
function isPathActive(node){
var paths, i, nextNode;
if(node.active){
if(node.layer === 2){ // is node at end
return true;
}
paths = Object.keys(node.connections).map(key => node.connections[key]);
for(i = 0; i < paths.length; i ++){
nextNode = paths[i].target;
if(nextNode.active){
if(nextNode.layer === 2){
return true;
}
if(isPathActive(nextNode)){
return true;
}
}
}
}
return false;
}
// renders from a node all active pathes to end
function renderPath(node){
var i;
paths = Object.keys(node.connections).map(key => node.connections[key]);
for(i = 0; i < paths.length; i ++){
nextNode = paths[i].target;
if(isPathActive(nextNode)){
drawLink(node,nextNode)
renderPath(nextNode);
}
}
drawNode(node,i+ 1)
}
// renders from top layer all active paths
function renderActivePaths(layer){
var i;
for(i = 0; i < layer.length; i ++){
if(isPathActive(layer[i])){
renderPath(layer[i])
}
}
}
renderActivePaths(layerList[0]);
}
var brain = {
counter: 0,
layers: 3,
globalReferenceNeurons: {},
globalReferenceConnections: {},
}
var layer0 = [new Neuron(brain, 0), new Neuron(brain, 0), new Neuron(brain, 0),
new Neuron(brain, 0), new Neuron(brain, 0), new Neuron(brain, 0),
new Neuron(brain, 0), new Neuron(brain, 0),new Neuron(brain, 0),
new Neuron(brain, 0)]; //10
var layer1 = [new Neuron(brain, 1), new Neuron(brain, 1), new Neuron(brain, 1),
new Neuron(brain, 1), new Neuron(brain, 1), new Neuron(brain, 1),
new Neuron(brain, 1), new Neuron(brain, 1), new Neuron(brain, 1),
new Neuron(brain, 1), new Neuron(brain, 1), new Neuron(brain, 1),
new Neuron(brain, 1)]; //13
var layer2 = [new Neuron(brain, 2), new Neuron(brain, 2)]; //2
layer0[0].connect(layer1[0]);
layer0[1].connect(layer1[1]);
layer0[2].connect(layer1[2]);
layer0[3].connect(layer1[3]);
layer0[4].connect(layer1[4]);
layer0[5].connect(layer1[5]);
layer0[6].connect(layer1[6]);
layer0[7].connect(layer1[7]);
layer0[8].connect(layer1[8]);
layer0[9].connect(layer1[9]);
layer0[0].connect(layer1[3]);
layer0[1].connect(layer1[4]);
layer0[2].connect(layer1[5]);
layer0[3].connect(layer1[6]);
layer0[4].connect(layer1[7]);
layer0[5].connect(layer1[8]);
layer0[6].connect(layer1[9]);
layer0[7].connect(layer1[10]);
layer0[8].connect(layer1[11]);
layer0[9].connect(layer1[12]);
layer1[0].connect(layer2[0]);
layer1[1].connect(layer2[1]);
layer1[2].connect(layer2[0]);
layer1[3].connect(layer2[1]);
layer1[4].connect(layer2[0]);
layer1[5].connect(layer2[1]);
layer1[6].connect(layer2[0]);
layer1[7].connect(layer2[1]);
layer1[8].connect(layer2[0]);
layer1[9].connect(layer2[1]);
layer1[10].connect(layer2[0]);
layer1[11].connect(layer2[1]);
layer1[12].connect(layer2[0]);
//works! until...
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
for (prop in brain.globalReferenceNeurons) {
var rand = getRandomInt(1,6);
var neuron = brain.globalReferenceNeurons[prop];
if (rand == 1 && neuron.layer != 2) neuron.active = false;
}
for (prop in brain.globalReferenceConnections) {
var rand = getRandomInt(1,6);
var connection = brain.globalReferenceConnections[prop];
if (rand == 1) connection.active = false;
}
renderBrain(brain, canvas.getContext("2d"), canvas);
<canvas id="canvas" width= 512 height = 200></canvas>

Canvas is not drawing images

I have problem with canvas, I'm trying to draw images but is not working as you can see I'm loading images into array and wait for all is loaded after this I'm changing my images each iteration but is not drawing any one, please look at my code. I cannot find error :(
(() => {
"use strict";
const images = [];
const promises = [];
const url = './assets/';
const canvas = document.getElementById("canvas");
const context = canvas.getContext("2d");
const FPS = 30;
const INTERVAL = 10000 / FPS;
const canvasDraw = () => {
let i = 0;
setInterval(() => {
context.drawImage(images[i] , 300, 300);
i++;
if (i === images.length) i = 0;
}, INTERVAL);
};
const loadImage = (image) => {
return new Promise((resolve) => {
const img = new Image();
img.src = url + image + '.png';
img.onload = function () {
images.push(img);
resolve();
};
});
};
for(let i = 1; i < 14; i++) {
promises.push(loadImage(i));
}
Promise
.all(promises)
.then(() => {
canvasDraw();
});
})();
and my html file contains canvas like this one
<canvas id="canvas"></canvas>
You need to give your canvas a width and height.
Using placeholder images:
(() => {
"use strict";
const images = [];
const promises = [];
const url = './assets/';
const canvas = document.getElementById("canvas");
const context = canvas.getContext("2d");
const FPS = 30;
const INTERVAL = 10000 / FPS;
const canvasDraw = () => {
let i = 0;
setInterval(() => {
context.drawImage(images[i] , 0, 0);
i++;
if (i === images.length) i = 0;
}, INTERVAL);
};
const loadImage = (image) => {
return new Promise((resolve) => {
const img = new Image();
img.src = 'https://placehold.it/' + (image * 20) + 'x' + (image * 20);
img.onload = function () {
images.push(img);
resolve();
};
});
};
for(let i = 1; i < 14; i++) {
promises.push(loadImage(i));
}
Promise
.all(promises)
.then(() => {
canvasDraw();
});
})();
<canvas id="canvas" width="300" height="300"></canvas>
Depending on what you're doing, you may want to clear the canvas between renderings.
(() => {
"use strict";
const images = [];
const promises = [];
const url = './assets/';
const canvas = document.getElementById("canvas");
const context = canvas.getContext("2d");
const FPS = 30;
const INTERVAL = 10000 / FPS;
const canvasDraw = () => {
let i = 0;
setInterval(() => {
context.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas
context.drawImage(images[i] , 0, 0);
i++;
if (i === images.length) i = 0;
}, INTERVAL);
};
const loadImage = (image) => {
return new Promise((resolve) => {
const img = new Image();
img.src = 'https://placehold.it/' + (image * 20) + 'x' + (image * 20);
img.onload = function () {
images.push(img);
resolve();
};
});
};
for(let i = 1; i < 14; i++) {
promises.push(loadImage(i));
}
Promise
.all(promises)
.then(() => {
canvasDraw();
});
})();
<canvas id="canvas" width="300" height="300"></canvas>

Javascript Image not rendered unless global

I'm trying to render an image from a sprite sheet using JS. The curious thing is, unless the object that does the rendering is global, it doesn't work (see code and comments). The behaviour is identical in both FF and Chrome.
resetGame() is executed on page load.
var TILE_SIZE = 24;
function CharacterImage(imageSource)
{
var tile_x = 0;
var tile_y = 0;
var img = new Image();
img.src = imageSource;
this.render = function(ctx, x, y)
{
ctx.drawImage(img, tile_x, tile_y, TILE_SIZE, TILE_SIZE,
x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
}
}
function Hero(canvas, image)
{
var ctx = canvas.getContext("2d");
var img = image;
this.render = function()
{
var x = 1;
var y = 1;
img.render(ctx, x, y);
}
}
// If the heroImage is constructed here, instead of within the function below,
// the image is rendered as expected.
var heroImage = new CharacterImage("img/sf2-characters.png");
function resetGame()
{
var heroCanvas = document.getElementById("heroLayer");
// On the otherhand, if the object is constructed here, instead of
// globally, the rendering doesn't work.
var heroImage = new CharacterImage("img/sf2-characters.png");
var hero = new Hero(heroCanvas, heroImage);
hero.render();
}
Oh hang on I think I see what's happening. The image needs time to load, so you should somehow bind an event to the loading of the image. This could be done as for example:
var TILE_SIZE = 24;
function CharacterImage(imageSource)
{
var tile_x = 0;
var tile_y = 0;
var img = new Image();
img.src = imageSource;
this.render = function(ctx, x, y)
{
ctx.drawImage(img, tile_x, tile_y, TILE_SIZE, TILE_SIZE,
x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
}
// Set up a "load" event for the img
this.loaded = function(callback) {
img.addEventListener('load', callback);
}
}
function resetGame()
{
var heroCanvas = document.getElementById("heroLayer");
var heroImage = new CharacterImage("img/sf2-characters.png");
var hero;
// Initiate the "load" event
heroImage.loaded(function() {
hero = new Hero(heroCanvas, heroImage);
hero.render();
};
}
What you'll probably want though is some sort of preloader "class"/event that keeps track of everything being loaded before you actually continue with rendering. It could look something like this.
var TILE_SIZE=60;
function Sprite(imageSource)
{
this.img = new Image();
this.img.src = imageSource;
this.position = { x:0, y:0 };
}
Sprite.prototype = {
isLoaded: function() {
return this.img.complete;
},
onLoad: function(callback) {
if (typeof callback !== "function") return;
if (this.isLoaded()) {
callback();
}
else {
this.img.removeEventListener('load', callback);
this.img.addEventListener('load', callback);
}
},
moveBy: function(x, y) {
this.position.x += x;
this.position.y += y;
},
render: function(ctx) {
if (!this.isLoaded()) return;
ctx.drawImage(this.img, this.position.x * TILE_SIZE, this.position.y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
}
};
function SpriteList()
{
this.list = {};
}
SpriteList.prototype = {
isLoaded: function() {
for (var i in this.list) {
if (!this.list[i].isLoaded()) {
return false;
}
}
return true;
},
_onLoadFunc: null,
onLoad: function(callback) {
this._onLoadFunc = callback;
this.onImageLoaded();
},
onImageLoaded: function() {
if (this.isLoaded() && typeof this._onLoadFunc === "function") {
this._onLoadFunc();
}
},
add: function(name, sprite) {
this.list[name] = sprite;
sprite.onLoad(this.onImageLoaded.bind(this));
},
get: function(name) {
return this.list[name];
}
};
var sprites = new SpriteList();
sprites.add("player", new Sprite("http://www.fillmurray.com/200/200"));
sprites.add("enemy", new Sprite("http://www.fillmurray.com/100/100"));
sprites.add("pickup", new Sprite("http://www.fillmurray.com/60/60"));
sprites.get("pickup").moveBy(1,2);
sprites.get("enemy").moveBy(2,0);
sprites.onLoad(function() {
document.getElementById("loading").innerHTML = "Loaded!";
var c = document.getElementById("ctx");
var ctx = c.getContext("2d");
sprites.get("player").render(ctx);
sprites.get("enemy").render(ctx);
sprites.get("pickup").render(ctx);
});
<div id="loading">Loading...</div>
<canvas id="ctx" width="200" height="200">
Anyways, that's why your code isn't firing, probably.

Categories