I am making a game engine called Forge.js. I have a Polygon method in a Entity class and it is not drawing the shapes. It loops through points and draws a line to each of the points. The lines however aren't being drawn. help plz
polygon method:
_polygon(points){
const local_ctx = new Path2D()
this.ctx.beginPath()
var j = 3
local_ctx.moveTo(points[0], points[1])
for (var i=0; i<=points.length; i++){
local_ctx.lineTo(points[i+2], points[i+j])
j += 2
}
this.ctx.fillStyle = constants.COLORS.black
this.ctx.fill()
}
As has been said in the comments, you need to pass the Path2D object to fill() in order for the context to draw it.
But this isn't the only issue with your code.
Your for loop probably doesn't do what you expected:
// Rewrote in order to log the indexes that are beign used in the loop
var j = 3
console.log(0, 1)
for (var i=0; i<=10; i++){
console.log(i+2, i+j)
j += 2
}
You can actually simplify this loop a lot by taking advantage of the fact that an empty subpath doesn't require an initial moveTo call. lineTo(x, y) gets automatically converted to moveTo(x, y) if your path is empty.
So we can treat all our points the same, all in a single for loop that only increments our index by 2.
const constants = { COLORS: { BLACK: "#000" } };
const obj = {
ctx: document.createElement("canvas").getContext("2d"),
_polygon(points){
const local_ctx = new Path2D()
// Since we're using a Path2D object we don't need to do anything
// on the context's subpath
// this.ctx.beginPath()
for (var i=0; i<=points.length; i+=2){
local_ctx.lineTo(points[i], points[i+1])
}
this.ctx.fillStyle = constants.COLORS.black
this.ctx.fill(local_ctx) // fill the context using the Path2D object
}
};
document.body.append(obj.ctx.canvas);
obj._polygon([13, 13, 13, 50, 50, 50]);
Related
I'm building a battleship game in Javascript and React and I've been stuck on this issue for a while now even after much Googling and StackOverflowing.
Basically my board is a 2D array, with 10 arrays inside of one array. I'm trying to randomly place ships and I'm having difficulties checking if a ship intersects another ship.
Here's what I have for my ships:
placeShips = () => {
// Logic to place boats randomly below
// Checks required before placing a boat:
// 1. Does the boat go off the board
// 2. Does the boat overlap another boat
// 3. If checks above pass then place boat
let placedPosition = []
let board = this.state.board.slice()
let i
for (i = 0; i < this.state.ships.length; i++) {
// First randomly select coordinates for where the boat will start
let xcoord = Math.floor(Math.random() * 10)
let ycoord = Math.floor(Math.random() * 10)
// Get positions in array where a boat will be
let potentialBoat = []
let newCoords
let j
for (j = 0; j < this.state.ships[i].getLength(); j++) {
newCoords = [xcoord, ycoord + j]
potentialBoat.push(newCoords)
The first for loop repeats for each ship left in my state to place and the second for loop takes a ship's length, gets the intended coordinate ([[0, 1], [0,2]] for a 2 length ship for example) and stores it in the potentialBoat array.
My idea is to use this potentialBoat array and see if there's any [xcoordinate, ycoordinate] that exists already in the placedPosition array and if so, to loop again for the current boat and get new coordinates until they don't intersect.
Is this possible? Or should I rethink my entire implementation? Thanks!
Inside the inner loop, when in the process of creating a ship, consider creating a string representing the coordinates. Eg, for newCoords of 1, 3, create a string 1_3. To validate the location, check to see if that string exists in an array (or Set) of the locations of the validated ships. At the end of the inner loop, once all positions for the length of the ship have been validated, combine the possible-locations into the validated-locations array:
placeShips = () => {
const placedPosition = [];
const board = this.state.board.slice();
const validatedPositionStrings = []; // <---- Create this array
for (const ship of this.state.ships) {
const thisShipLength = ship.getLength();
tryShip:
while (true) {
const thisBoatPossiblePositionStrings = [];
// Generate ship positions until valid
const xcoord = Math.floor(Math.random() * 10);
const ycoord = Math.floor(Math.random() * 10);
const potentialBoat = [];
for (let j = 0; j < thisShipLength; j++) {
// Then check to see if the below position is already in it
const thisCoordinateString = `${x}_${y}`;
if (validatedPositionStrings.includes(thisCoordinateString)) {
// Invalid
continue tryShip;
}
thisBoatPossiblePositionStrings.push(thisCoordinateString);
// If this point is reached, then this particular coordinate is valid
// do whatever you need to do:
const newCoords = [xcoord, ycoord + j];
potentialBoat.push(newCoords);
}
// All positions for ship are valid
// do something with potentialBoat here?
// push positions to placedPosition?
validatedPositionStrings.push(...thisBoatPossiblePositionStrings);
break;
}
}
}
It could be made less computationally complex by using a Set instead of an array, but that probably doesn't matter unless there are a very large number of iterations.
It would also be possible to search your array of arrays to see if the position has already been placed, but that would require an unnecessary amount of code IMO.
If possible, you might consider changing your data structure around so that rather than an array of arrays, you have just a single object representing the coordinates, whose values indicate the ship at that position (and possibly other attributes needed for a particular point), eg:
{
1_3: { ship: 'destroyer', 'attackedYet': 'false' }
// ...
Such an object would probably be easier to look up and work through than an array of arrays of X-Y pairs.
I am trying to create an array of vertices, and then run the 'rect' function over this array to display an arbitrary amount of rectangles. Right now, I have:
var vertices = new Array();
function setup() {
createCanvas(600, 600);
...
iter(width/2 - c/2, height/2 - c/2, c);
var i;
for (i = 0; i < vertices.length; i++) {
fill(200);
rect(vertices[i]);
}
}
And then:
function iter(x, y, len) {
r_1 = random(0, 1);
if (r_1 < 0.5){
vertices.push(x, y - len*0.5, len*0.5, len*0.5);
}
}
I have seen lots about using map or foreach to run functions over arrays but I don't know why this doesn't work (specifically, using a for loop to run a function over an array). I am obviously very new to all this stuff! An explanation of what I seem to misunderstand would be very much appreciated.
Thanks
When you do
vertices.push(x,y-len*0.5,len*0.5,len*0.5)
you're calling push with four arguments, so four items get pushed to the array. Because you're calling rect with verticies[i] later, it sounds like each item of verticies should be a data container of some sort - an array or an object, otherwise the points of each vertex will be separated out over multiple indicies. For example, if you were to use an array:
function iter(x, y, len) {
r_1 = random(0, 1);
if (r_1 < 0.5){
vertices.push([x, y - len*0.5, len*0.5, len*0.5]);
}
}
And then spread each array in the vertex array into the rect argument list:
function setup() {
createCanvas(600, 600);
// ...
iter(width/2 - c/2, height/2 - c/2, c);
var i;
for (i = 0; i < vertices.length; i++) {
fill(200);
rect(...vertices[i]);
}
}
This assumes that rect is a function that accepts 4 arguments. (you could also change rect so that it accepts a single array as an argument instead, and avoid the spread syntax, if you wanted)
You also might consider using an array literal rather than new Array - calling the Array constructor is rarely a good idea:
var vertices = [];
I have searched all over, and I have found answers for rectangle circle and sprite collisions. Nothing that provides collision detection between two arrays of points like for example,
var poly1=[
[0,0],
[20,50],
[50,70],
[70,20],
[50,0]
];
// each point from one to the next represent a line in the shape, then the last point connects to the first to complete it.
var poly2=[
[50,30],
[40,90],
[70,110],
[90,70],
[80,20]
];
var collided=arraysCollided(poly1,poly2);
Does anyone know of a library that can do just this? My research has come up with nothing that supports just that, and isnt associated with some game engine library.
For example a collision is triggered true when one or more points is inside the polygon of the other.
SAT.js was the anser for me, I just put every point into SAT.Vector then into SAT.Polygon, then test them with SAT.testPolygonPolygon(SAT.Polygon,SAT.Polygon);
var poly1={
name:"poly2",
x:400,
y:60,
rotation:0,
runner:function(){
},
points:[
[20,-50],
[-30,-50],
[-30,30],
[10,60],
[50,20]
]
};
var poly2={
name:"poly2",
x:50,
y:70,
runner:function(){
this.x+=1;
},
points:[
[-20,-40],
[-60,50],
[10,70],
[50,30],
[30,-20]
]
};
pGon=(poly)=>{
var center=SAT.Vector(0,0);
var pts=[];
for(var i in poly.points){
var point=poly.points[i];
// point = [0:x,1:y]
pts[pts.length]=new SAT.Vector(point[0]+poly.x,point[1]+poly.y);
}
var poly_a=new SAT.Polygon(center,pts);
return poly_a;
};
pCollide=(p1,p2)=>{
var p1_poly=pGon(p1);
var p2_poly=pGon(p2);
var res=new SAT.Response();
var collided=SAT.testPolygonPolygon(p1_poly,p2_poly,res);
console.log(collided);
console.log(res);
return collided;
};
var collided=pCollided(poly1,poly2);
With that, it maps each point to a polygon on the coordinate system, then tests it from there. So collided = true
I checked for if each point of each polygon is in the other polygon. This is the code for checking if a point is in a polygon:
function pip(x, y, polygon) {
let odd = false;
let v = polygon.va; //The vertices array
let j = v.length - 2;
for (let i=0; i<v.length-1; i+=2) {
if ((v[i+1]<= y && v[j+1]>=y || v[j+1]<= y && v[i+1]>=y)
&& (v[i]<=x || v[j]<=x)) {
odd ^= (v[i] + (y-v[i+1])*(v[j]-v[i])/(v[j+1]-v[i+1])) < x;
}
j=i;
}
if(odd === false) odd = 0;
return odd;
}
I got this from Here, but modified it to work for an array like this [x1,y1,x2,y2,x3,y3...]. To make it work for x,y pairs, you would just change the for loops modifier thing and look at polygon.va[i][0] as x and polygon[i][1] as y.
I'm doing an analysis of the presidential candidates speeches. I have a data file with the following variables:
> names(cl.context)
[1] "id" "category" "statement" "nchar" "polarity"
The statement variable is populated by sentences that each belong to one category from three. The polarity ranges from -1 to 1, reflecting whether the sentence has a positive bias, neutral, or negative bias.
What I'm trying to do in p5 is to have statements displayed by category, with random x,y placement, when the user clicks the mouse INSIDE the canvas. The statements themselves are colored according to their polarity.
I finally got to the point where the developer console doesn't throw any errors and it draws the canvas. But when I click within the canvas, nothing happens. No statements appear.
I'm VERY new to JavaScript, and since it's not throwing an error message, I can't resolve where the issue lies. Hoping for some advice here.
My p5 code:
var clContext;
var x;
var y;
const STATEMENTS = 118, CATEGORY = 3, QTY = STATEMENTS/CATEGORY | 0,
POLARITY = 3,
statements = Array(STATEMENTS), inds = Array(CATEGORY), polarity = Array(POLARITY);
//load the table of Clinton's words and frequencies
function preload() {
clContext = loadTable("cl_context.csv", "header");
}
function setup() {
createCanvas(647, 400);
background(51);
// Calling noStroke once here to avoid unecessary repeated function calls
noStroke();
// iterate over the table rows
for(var i=0; i<clContext.getRowCount(); i++){
//- Get data out of the relevant columns for each row -//
var inds = clContext.get(i, "category");
var statements = clContext.get(i, "statement");
var polarity = clContext.get(i, "polarity")
}
for (let i = 0; i < statements; randomCategoryStates(i++));
// create your Statement object and add to
// the statements array for use later
inds[i] = new Statement();
console.info(inds);
}
function draw() {
if(mouseClicked == true){
for(var i=0; i<inds.length; i++) {
inds[i].display();
}
}
}
function mouseClicked() {
if((mouseX < width) && (mouseY < height)) {
randomCategoryStates(~~random(CATEGORY));
redraw();
return false;
}
}
// Function to display statements by a random category with each mouse click
function randomCategoryStates(group) {
let idx = inds[group], rnd;
while ((rnd = ~~random(QTY)) == idx);
inds[group] = rnd;
}
// Function to align statements, categories, and polarity
function Statement() {
this.x = x;
this.y = y;
this.xmax = 10;
this.ymax = 4;
this.cat = inds;
this.statement = statements;
this.polarity = polarity;
// set a random x,y position for each statement
this.dx = (Math.random()*this.xmax) * (Math.random() < .5 ? -1 : 1);
this.dy = (Math.random()*this.ymax) * (Math.random() < .5 ? -1 : 1);
}
// Attach pseudo-class methods to prototype;
// Maps polarity to color and x,y to random placement on canvas
Statement.prototype.display = function() {
this.x += this.dx;
this.y += this.dy;
var cols = map(this.polarity == -1, 205, 38, 38);
var cols = map(this.polarity == 0, 148, 0, 211);
var cols = map(this.polarity == 1, 0, 145, 205);
fill(cols);
textSize(14);
text(this.statement, this.x, this.y);
};
EDIT: One thing that confused me is that the help I got with this code on the Processing forum didn't include a call for the mouseClicked() function in the draw function, so I added that. Not entirely sure that I did it correctly or if it was even necessary.
Your code has a lot going on. I'm going to try to go through everything, in no particular order:
Why do you need these variables?
var x;
var y;
I know that you think you're using them to pass in a position to a Statement, but you never set these variables! Let's just get rid of them for now, since they aren't doing anything. This will cause an error in your code, but we'll fix that in a second.
Look at this for loop:
for(var i=0; i<clContext.getRowCount(); i++){
//- Get data out of the relevant columns for each row -//
var inds = clContext.get(i, "category");
var statements = clContext.get(i, "statement");
var polarity = clContext.get(i, "polarity")
}
Here you're reading in the CSV file, but then you aren't doing anything with these variables. You then follow that up with this:
for (let i = 0; i < statements; randomCategoryStates(i++));
// create your Statement object and add to
// the statements array for use later
inds[i] = new Statement();
Notice the semicolon after that for loop! That means the inds[i] = new Statement() line is outside the loop, which doesn't make any sense. I also don't really know what you're doing with the randomCategoryStates(i++) part.
You need to combine all of that into one loop:
for (var i = 0; i < clContext.getRowCount(); i++) {
var category = clContext.get(i, "category");
var statement = clContext.get(i, "statement");
var polarity = clContext.get(i, "polarity")
inds[i] = new Statement();
}
But this still doesn't make any sense, because you're never passing those variables into your Statement class. So let's take a look at that.
I'll just add some comments:
function Statement() {
this.x = x; //when do you ever set the value of x?
this.y = y; //when do you ever set the value of y?
this.cat = inds; //cat is the array that holds all statements? What??
this.statement = statements; //statement is the statements array, but nothing is ever added to that array?
this.polarity = polarity; //when do you ever set the value of polarity?
As you can see, what you're doing here doesn't make a lot of sense. You need to change this constructor so that it takes arguments, and then you need to pass those arguments in. Something like this:
function Statement(category, polarity, statement) {
this.category = category;
this.statement = statement;
this.polarity = polarity;
}
Now that we have that, we can change the line in our for loop to this:
inds[i] = new Statement(category, statement, polarity);
But that still doesn't make sense. Why do you have separate arrays for statements, categories, and polarities? Don't you just want one array that holds them all, using instances of the Statement class? So let's get rid of the inds and polarity variables, since they aren't used for anything.
We then change that line to this:
statements[i] = new Statement(category, polarity, statement);
We also have to change everywhere else that's still using the inds variable, but we have other problems while we're at it.
Let's just start with your draw() function:
function draw() {
if (mouseClicked == true) {
for (var i = 0; i < statements.length; i++) {
statements[i].display();
}
}
}
So I guess you only want anything to display while the mouse is pressed down, and nothing to display when the mouse is not pressed down? I'm not sure that makes sense, but okay. Even so, this code doesn't make sense because mouseClicked is a function, not a variable. To determine whether the mouse is pressed, you need to use the mouseIsPressed variable, and you don't need the == true part.
if (mouseIsPressed) {
I have no idea what these two functions are supposed to do:
function mouseClicked() {
if ((mouseX < width) && (mouseY < height)) {
randomCategoryStates(~~random(CATEGORY));
redraw();
return false;
}
}
// Function to display statements by a random category with each mouse click
function randomCategoryStates(group) {
let idx = statements[group],
rnd;
while ((rnd = ~~random(QTY)) == idx);
statements[group] = rnd;
}
There are much simpler ways to get random data. I'm just going to delete these for now, since they're more trouble than they're worth. We can go back and add the random logic later.
For now, let's look at the display() function inside your Statement class:
Statement.prototype.display = function() {
this.x += this.dx;
this.y += this.dy;
var cols = map(this.polarity == -1, 205, 38, 38);
var cols = map(this.polarity == 0, 148, 0, 211);
var cols = map(this.polarity == 1, 0, 145, 205);
fill(cols);
textSize(14);
text(this.statement, this.x, this.y);
};
We never actually declared the x, y, dx, or dy, variables, so let's add them to the constructor:
this.x = random(width);
this.y = random(height);
this.dx = random(-5, 5);
this.dy = random(-5, 5);
Back to the display() function, these lines don't make any sense:
var cols = map(this.polarity == -1, 205, 38, 38);
var cols = map(this.polarity == 0, 148, 0, 211);
var cols = map(this.polarity == 1, 0, 145, 205);
Why are you declaring the same variable 3 times? Why are you trying to map a boolean value to a number value? This doesn't make any sense. For now, let's just get rid of these lines and simplify your logic:
if(this.polarity == -1){
fill(255, 0, 0);
}
else if(this.polarity == 1){
fill(0, 255, 0);
}
else{
fill(0, 0, 255);
}
This will make negative polarity red, positive polarity green, and neutral polarity blue.
Now that we have this, we can actually run the code. When you hold the mouse down, you'll see your statements display and move around randomly. However, they'll quickly fill up your screen because you aren't ever clearing out old frames. You need to call the background() function whenever you want to clear out old frames. We might do that at the beggining of the draw() function, or right inside the if(mouseIsPressed) statement, before the for loop.
if (mouseIsPressed) {
background(51);
for (var i = 0; i < statements.length; i++) {
If you make all those changes, you will have a working program. I'd be willing to bet that it still doesn't do exactly what you want. You're going to have to start much simpler. Your code is a bit of a mess, and that's a result of trying to write the whole program all at once instead of testing small pieces one at a time. This is why we ask for an MCVE, because debugging the whole thing like this is very painful. You need to start narrowing your goals down into smaller pieces.
For example, if you now want to make it so only one statement appears at a time, start over with a simpler example sketch that only shows one hardcoded statement. Get that working perfectly before you try to integrate it into your main program. If you want the statements to be ordered by category, then start over with a simpler example sketch that only shows statements based on category, without any of the extra logic. That way if you have a question about something specific, you can post that small code and it will be much easier to help you.
Good luck.
Question: How can I make putImageData() update the canvas in real time, as various parts of the image have been computed?
I am working on a JavaScript/TypeScript application to draw the Mandelbrot set on an HTML5 <canvas> element. Math and details aside, my application draws the set just fine. However, if you are familiar with visualizing the set, you know that it can take a long time to draw.
It will draw in a few seconds, but until then, the canvas is completely blank, then the image appears. I'm looking for a way to draw each row as it is computed using putImageData(). Here is what I am trying:
// part of the class definition
private Context: CanvasRenderingContext2D;
private ImageData: ImageData;
private Pixels: number[];
constructor() {
var c: HTMLCanvasElement = <HTMLCanvasElement>document.getElementById("can");
this.Context = c.getContext("2d");
this.ImageData = this.Context.createImageData(this.Size.Width, 1);
this.Pixels = this.ImageData.data;
}
public draw() {
// tried this... does not help
// var handler = function(m: Mandelbrot) {
// m.Context.putImageData(m.ImageData, 0, i)
// };
for(var i: number = 0; i < this.Size.Height; ++i) { // Loop over each row
for(var j: number = 0; j < this.Size.Width; ++j) { // Calc px. for one row
// all the math to compute the set... (works)
this.setPixelColor(j, color); // sets a color in this.Pixels (works)
}
// setTimeout(handler(this), 0); // does not help
this.Context.putImageData(this.ImageData, 0, i); // Draw the row on the canvas?
}
}
Somehow, the putImageData() function, which is called after a row in the image has been computed, only shows the image after the entire image has been generated.
How can I make putImageData() update the canvas in real time, as each row has been computed?
Latest update of non-working code:
var handler = function(m: Mandelbrot, i: number) {
for (var j: number = 0; j < m.Size.Width; ++j) {
// math
m.setPixelColor(j, color);
}
m.Context.putImageData(m.ImageData, 0, i);
};
var that: Mandelbrot = this;
for(var i: number = 0; i < this.Size.Height; ++i) {
setTimeout(function() {
handler(that, i)
}, 0);
}
Working code, thanks to ekuusela:
var handler = function(m: Mandelbrot, i: number) {
return function() {
for (var j: number = 0; j < m.Size.Width; ++j) {
// math
m.setPixelColor(j, color);
}
m.Context.putImageData(m.ImageData, 0, i);
}
};
for(var i: number = 0; i < this.Size.Height; ++i) {
setTimeout(handler(this, i), 0);
}
Try wrapping putImageData and the calculation for a single row to a setTimeout call to execute it asynchronously (post accept edit: see the final code in the question, this won't work since i will be undefined in the putImageData row)
public draw() {
var that = this;
var drawRow = function() {
for(var j: number = 0; j < that.Size.Width; ++j) { // Calc px. for one row
that.setPixelColor(j, color); // sets a color in this.Pixels (works)
}
// TODO specify the dirty region in this call
that.Context.putImageData(that.ImageData, 0, i); // Draw the row on the canvas?
};
for(var i: number = 0; i < this.Size.Height; ++i) { // Loop over each row
setTimeout(drawRow, 0);
}
}