I have problem with javascript canvas.
I'm trying to put on canvas several object which are moving and bouncing on edges of canvas. If i create one object, there is no problem with it, but if i want to create more objects, they become connected.
Fiddle example:
https://jsfiddle.net/mwbgwa39/
I will be very thankfull if someone could help me :)
Important thing is to call beginPath() at the begining of each object drawing and closePath() when you are done drawing that object. If you don't call it canvas thinks that you are trying to continue drawing last object. You can read more on MDN
for(i in obj_t)
{
ctx.beginPath();
obj_t[i].xPos = obj_t[i].xPos + obj_t[i].xVel;
obj_t[i].yPos = obj_t[i].yPos + obj_t[i].yVel;
ctx.arc(obj_t[i].xPos, obj_t[i].yPos, obj_t[i].r, 0, 2 * Math.PI);
ctx.stroke();
ctx.fill();
ctx.closePath(); // optional
}
EDIT: As Kaiido noticed calling closePath() in this example is not necessary. More info on when you should call closePath() can be found in this SO question.
You're drawing a path. Each time you draw something it continues the path you have made so far. You can move beginPath into the for loop to start a new path for each circle:
ctx.clearRect(0, 0, 400, 400);
for(i in obj_t)
{
ctx.beginPath();
// draw here
}
ctx.closePath();
See in action: https://jsfiddle.net/mwbgwa39/4/
Related
I'm hacking on a vector car game in javascript and html canvas.
The point is that you click on of the blue squares and the car goes there - simple!
The outer and inner borders are two arrays of xy points that i've drawn out on the canvas
I've come to the point where i need to figure out if the car is on the course or not.
I've tried a bunch of different things, but just cant get this to work. Its the diagonal lines that makes my head hurt.
Can someone point me in the right direction how i would go about doing this?
You don't need to post any code, just some guidelines on which approach to take and how to calculate.
You can use a Path2D object combined with even-odd fill-rule and isPointInPath(). This allow you to define the test-path once and not have concerns about what you draw on the main context. If your path changes simply redefine the path object accordingly.
First define the two paths on the path object (not context). Separate them using moveTo() for the second path. You may want to use closePath() as well if you plan to stroke (for testing this will be implicit).
Test using the path and the even-odd fill rule:
if (ctx.isPointInPath(path, x, y, "evenodd")) { /* inside */ };
var ctx = c.getContext("2d");
var path = new Path2D();
path.arc(75, 75, 74, 0, 6.28); // replace with the polygons
path.closePath();
path.moveTo(75 + 40, 75);
path.arc(75, 75, 40, 0, 6.28);
path.closePath();
ctx.stroke(path);
ctx.globalCompositeOperation = "copy";
window.onmousemove = function(e) {
ctx.strokeStyle = ctx.isPointInPath(path, e.clientX, e.clientY, "evenodd") ? "red" : "#000";
ctx.stroke(path);
};
html, body {margin:0}
<canvas id=c></canvas>
You could use CanvasRenderingContext2D#isPointInPath() to check wether a Point is on the track or not.
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/isPointInPath
To do so you might need to refactor your Code in a manner so that the track is drawn as a separate path. Additionally, isPointInPath() must be called right after the path has been drawn and before any furthor path is drawn, because isPointInPath() is applied to the current path on the state-stack.
I am testing to see if the mouse is located on an object. The problem is the object has been transformed. I have graph of objects, mainly the camera, then the slider object, and finally the shape object. I need to be able to see if the mouse coordinates are inside a specified rectangle relative to the shape object.
Here I have my game loop which transforms the clears the canvas then transforms the camera. I then go into a for loop and loop through all the objects calling their specific "draw" method, passing in the context that has been transformed.
Game.prototype.gameLoop = function()
{
this.context.clearRect(0,0,this.canvas.width, this.canvas.height);
this.context.save();
this.context.translate(this.canvas.width/2, this.canvas.height/2);
this.context.scale(this.camera.scale,this.camera.scale);
this.context.rotate(this.camera.rotate);
this.context.translate(this.camera.x,this.camera.y);
for(var i=0;i<this.objects.length;i++)
{
this.objects[i].update();
this.objects[i].draw(this.context);
}
this.context.restore();
}
Here is one of the objects draw method. The object is called a Slider. It successfully is called and performs a transformation based on it's x,y, and rotate values.
Slider.prototype.draw = function(ctx)
{
ctx.save();
ctx.translate(this.x,this.y);
ctx.rotate(this.rotate);
this.pointer.draw(ctx);
ctx.fillStyle = "black";
ctx.beginPath();
ctx.moveTo(-(this.width/2),0);
ctx.lineTo((this.width/2),0);
ctx.lineTo((this.width/2),5);
ctx.lineTo(-(this.width/2),5);
ctx.fill();
ctx.restore();
}
Finally I have the Shape's draw method which successfully is called and transforms the context yet again.
Shape.prototype.draw = function(ctx)
{
ctx.save();
ctx.translate(this.x,this.y);
ctx.rotate(this.rotate);
if(this.isMouseOver)
ctx.fillStyle = this.color;
else
ctx.fillStyle = this.mouseOverFillColor;
ctx.fill(this.shape);
ctx.restore();
}
And lastly, here is the method that gets called when the mouse moves called "mouseEventListener". I need to be able to transform the coordinates to see them relative to the shape.
Shape.prototype.mouseEventListener = function(evt,type)
{
console.log(evt.clientX+" "+evt.clientY);
}
Any ideas? If needed I can create a parent pointer object and have the shape point to the slider and the slider point to the camera to access each parent's x,y, rotate vales.
I am kind of looking for the equivalent of Android's mappoints method, which transforms points based off a matrix. In this case the context has been transformed multiple times and I need a way to capture that state for each object, and then transform some points.
I would also like to do all this easily without any other libraries.
Thank you.
I was trying out a simple script to create a rectangle and it seemed to work find except that the final leg of the polygon doesn't connect with the first leg properly. The strange thing is that all the other legs do connect properly.
Before anyone suggests that I use the built-in drawRect() or similar function, I should emphasize that this is just a simple example of a far more complex figure I'm trying to draw. I'm just confused why this is happening.
newX = 10;
letterHeight = 100;
ctx.strokeStyle = "#999999";
ctx.lineWidth = 6;
ctx.moveTo(newX,letterHeight*0.5);
ctx.lineTo(newX+letterWidth,letterHeight*0.5);
ctx.lineTo(newX+letterWidth,letterHeight);
ctx.lineTo(newX, letterHeight);
ctx.lineTo(newX, letterHeight*0.5);
ctx.stroke();
It seems to me that this probably has something to do with the line width and I could consider some problem-specific crude fix for that but is there a more general fix if that's the case?
Link to how the script rendered (using firefox)
Use ctx.closePath(); just before the stroke command to close the path. You should also be starting the path with ctx.beginPath();
Html:
HTML:
<canvas id="main" height="1000" width="1500"></canvas>
JS
var c = document.getElementById("main");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.moveTo(200,300);
ctx.lineTo(800,0);
ctx.moveTo(800,0);
ctx.lineTo(1500,300);
ctx.moveTo(1500,300);
ctx.lineTo(200,300);
ctx.closePath();
ctx.stroke();
ctx.fillStyle="#8ED6FF";
ctx.fill();
JSFiddle
Fiddle
Updated code
var c = document.getElementById("main");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.moveTo(200,300);
ctx.lineTo(800,0);
ctx.lineTo(1500,300);
ctx.lineTo(200,300);
ctx.closePath();
ctx.stroke();
ctx.fillStyle="#8ED6FF";
ctx.fill();
You dont need to move to same point again as you are already on that point .. so thats why it wasnt working
Sub-paths
This will create (clear) a new main path, so far so good:
ctx.beginPath();
The moveTo() call will create a new sub-path on the main path, here:
ctx.moveTo(200,300);
ctx.lineTo(800,0);
and here:
ctx.moveTo(800,0);
ctx.lineTo(1500,300);
and here:
ctx.moveTo(1500,300);
ctx.lineTo(200,300);
And finally, this will connect first point in path with the last point (in this case they are already overlapping):
ctx.closePath();
Since you now have three sub-paths which represents three unconnected lines, as they all have their own sub-paths, there is no way to fill them as a single shape. And they will not connect simply by having their points overlapping.
Create a continuous line with a single sub-path
You need to make a continuous line on the current sub-path. The lineTo() method will continue from the last point in the current path/sub-path to the coordinate it specifies, so to make a single shape using a single sub-path, you just add a new point to the sub-path by doing:
ctx.beginPath(); // new main path
ctx.moveTo(200,300); // creates a new sub-path, start point (200, 300)
ctx.lineTo(800,0); // line from (200, 300) to (800, 0), current point now: (800,0)
ctx.lineTo(1500,300); // line from (800, 0) to (1500, 300)
//ctx.lineTo(200,300); // not needed because:
ctx.closePath(); // will connect (1500,300) to (200,300) on the current sub-path
Using fill() will also close the path, internally and non-permanent, as it's impossible to fill an open path assuming it has > 2 points (it saves you a line of code, but not very important, just note that stoke() does not close the path for you internally).
Also, a tip not many are aware of: if you intended to draw more closed shapes in the same fillStyle, you could continue with moveTo() after the closePath() to create a new sub-path without the need of using fill()/beginPath() first..
OK, I admit I tried to be clever: I thought if I overrode Shape's drawFunc property I could simply draw whatever inside a rectangle and still use KineticJS's click detection. Here's my attempt:
var shape = new Kinetic.Shape({
drawFunc: function(context) {
var id = 26; // Id of a region inside composite image.
context.beginPath();
context.rect(0, 0, w, h);
context.closePath();
this.fill(context);
this.stroke(context);
context.drawImage(copyCanvas, (id % 8) * w, flr(id / 8) * h,
w, h, 0, 0, w / 2, h / 2);
},
draggable: true
});
So, the idea was to draw a rectangle, and use drawImage() to draw something on top of the rectangle (like a texture, except it changes from time to time because copyCanvas itself changes). All the meanwhile, I expected event handling (drag-n-drop, in particular) to still 'just work'. Well, here's what happens: the part of the rectangle not covered by my drawImage() correctly detects clicks. However, the one fourth of the rectangle that is covered by the image refuses to respond to clicks! Now, my question is why? I dug into the KineticJS code, and looked to me that click detection simply means drawing to a buffer and seeing if a given x, y point has non-zero alpha. I can't see how this could be affected by my drawing an image on top of my rectangle.
Any ideas what's going on?
OK, so I went ahead and looked at the source code. Here's the definitive answer:
KineticJS assigns a random and unique RGB color to each shape that's created using a global map from RGB colors to shape objects. The draw() function of the shape is called twice: once with the 'real' canvas, and once with a 'buffer' canvas used for hit detection. When using the 'buffer' canvas, KineticJS switches the stroke and fill colors to the unique RGB color of the given shape. The same 'buffer' canvas is used for all shapes on a layer. Thus hit detection simply becomes reading the RGB value of a given point and looking up the corresponding shape in the global map. Now, in my example I drew an image in a way that circumvented KineticJS's juggling of colors used for hit detection. Thus, when I clicked on the image area, KineticJS saw some unknown RGB color on the buffer canvas with no known shape assigned to it.
The solution is not to draw the image for the 'buffer' (or 'hit detection') phase: a simple rectangle will do. In case you're wondering, here's the correct code for the drawFunc:
var width = 200;
var height = 100;
var myShape = new Kinetic.Shape({
drawFunc: function(context) {
if (layer.bufferCanvas.context == context) {
context.beginPath();
context.rect(0, 0, width, height);
context.closePath();
this.fill(context);
this.stroke(context);
} else {
context.drawImage(someCanvasWithAnythingOnIt, 0, 0, width, height,
0, 0, width, height);
}
}});
Can I collect my own reward?
I think your problem lies in the order. There is a depth associated with each object that you draw and the default ordering is like a stack, last drawn is on top.
Now that you have modified the code, making 2 draws inside the shape draw function, I still think the ordering is preserved and hence, the object is not able to detect the input. Try changing the order, i.e. draw image first and then the rectangle and see if the problem is solved.
Else, share a jsFiddle for an example.