KineticJS click detection inside animated shapes - javascript

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.

Related

How to transform a rectangle into a circle

I want to turn a rectangular image into a circle using the HTMLCanvas element. (Finally I only need the upper half of the circle but that could be easily managed by cutting the resulting circle in half.)
From this
To this
My idea was to do a simple line by line transformation. So far I have just the basic drawing logic but I'm totally lost with the math for the transformation.
<!DOCTYPE html>
<body>
<canvas id="canvas"></canvas>
</body>
<script type="text/javascript">
var img = new Image();
img.onload = function init() {
var img = this;
var imgH = img.height;
var imgW = img.width;
// make the canvas the same size as the image.
var c = document.getElementById("canvas");
c.width = imgW;
c.height = imgH;
var ctx = c.getContext("2d");
var halfHeight = imgH/2;
// draw the upper part
// line by line
for(var i = 0; i < halfHeight; i++) {
// I'm totally lost here.
// current output without transformation
ctx.drawImage(img, 0, i, imgW, 1, 0, i, imgW, 1);
}
// add the second half which must not be transformed
ctx.drawImage(img, 0, halfHeight, imgW, halfHeight, 0, halfHeight, imgW, halfHeight);
};
img.src = "https://i.stack.imgur.com/52TjZ.png";
</script>
</html>
A fiddle
https://jsfiddle.net/kirschkern/amq7t6ru/2/
(I need it in pure JS and 2d. No three.js, no webgl.)
Any help is highly appreciated.
I don't know much about Javascript but as this seems more of a mathematical question, I'll have my shot.
Replace the lines
// I'm totally lost here.
// current output without transformation
ctx.drawImage(img, 0, i, imgW, 1, 0, i, imgW, 1);
with
var xMargin = -Math.sqrt(1-Math.pow((i-halfHeight)/halfHeight,2))*imgW/2+imgW/2;
ctx.drawImage(img, 0, i, imgW, 1, xMargin, i, imgW-(2*xMargin), 1);
This distorts the upper half of the image as an ellipse (a circle would work only if your input image would be a square) as this:
Does this solve your question?
Explanation
I took the equation of a shifted ellipse from Wikipedia and set c1 and a to be equal to imgW/2 and c2 and b to imgH/2. Taking i for y let me compute x; I saved one of the solutions as xMargin. The width of the picture at the given vertical coordinate would be the original width minus twice the margin.
In the end, I fed drawImage() with these inputs, see the documentation.
Plain 2D JavaScript does not have primitives to distort images like this. So a simple drawImage will not be enough.
What you can do is approximate things. Write a function which for every point in the distorted image (the one with the circle) computes the corresponding position in the original image. Then you can do one of four things, in increasing order of effort and resulting quality.
Iterate over all the pixels in the destination image, and look up the corresponding pixel value in the source image.
Like before, but with subsampling: take several positions inside the square of the source pixel, and average the resuling colors for a smoother appearance.
Approximate the affine transformation in a given point (for this you will likely need partial derivatives of your mapping function) and use it to draw an affinely transformed image.
Same as 3 but with projective instead of affine transforms. That would arguably make it 3D in its formulation.
Like 1 or 2 but implement all of that in WebGL as a fragment shader. I know you said you don't want that, but in terms of performance and resulting quality this should give the best results.

Figure out if point is inside of shape

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.

Best way to transform mouse coordinates to HTML5 Canvas's transformed context

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.

How to change color of a sprite over time in melonJS

I want to add some trail effect to a moving object that will fade over time.
This is what I've got so far:
game.Trail = me.Entity.extend({
init:function (x, y, settings)
{
this._super(me.Entity, 'init', [
x, y,
{
image: "ball",
width: 32,
height: 32
}
]);
(new me.Tween(this.renderable))
.to({
alpha : 0,
}, 5000)
.onComplete((function () {
me.game.world.removeChild(this);
}).bind(this))
.start();
},
update : function (dt) {
this.body.update(dt);
return (this._super(me.Entity, 'update', [dt]) || this.body.vel.x !== 0 || this.body.vel.y !== 0);
}
});
Demo (move with WASD or arrow keys)
Here is a link to the full project to test locally.
But I want to change the colors of the items in the trail in the same way the fading is done.
In phaser this could be done tinting the sprite, but I have no clue about how to achieve that on melonjs.
Note: if the effect can be done with basic shapes instead of images that will work too.
With the melonJS canvas renderer, you'll have to add tinting by overriding the draw method on your sprite or renderable object. The CanvasRenderingContext2D API has some useful utilities for doing RGBA fills and so on that can tint the destination canvas. Since "tinting" is not built into melonJS, you'll need to keep a temporary canvas context to tint your sprites non-destructively.
Minimal example (non-destructive, but does not handle transparency well):
draw : function (renderer) {
renderer.save();
// Call superclass draw method
this._super(me.Entity, "draw", [ renderer ]); // XXX: Assumes you are extending me.Entity
// Set the tint color to semi-transparent red
renderer.setColor("rgba(255, 0, 0, 0.5)");
// Fill the destination rect
renderer.fillRect(this.pos.x, this.pos.y, this.width, this.height);
renderer.restore();
}
A more involved option is using the CanvasRenderingContext2D API to create the temporary canvas context; copy the original sprite to the texture, apply the tint while respecting transparency, using clip if you have to.
In the melonJS WebGL renderer, you just have to change the value of the global renderer color before the draw and restore the original value after. Minimal example:
draw : function (renderer) {
renderer.save();
// Set the tint color to semi-transparent red
renderer.setColor("rgba(255, 128, 128, 1)");
// Call superclass draw method
this._super(me.Entity, "draw", [ renderer ]); // XXX: Assumes you are extending me.Entity
renderer.restore();
}
This works in WebGL because the shader is already setup to multiply the source image by the global color value. You'll get a different color result from the CanvasRenderer option above because WebGL is happiest with premultiplied alpha. (In this example, the value of the blue and green components in the source image will be reduced by half, making the sprite appear more red.)
Feel free to play with it a bit, but in general you'll get far more control over rendering in WebGL, and in fact you have the option to customize the compositor and shaders if you need to do really crazy effects.

Changing Pattern Size in Html5 Canvas

My Problem: I've got an ImageObject, that is being used to create a PatternObject. My problem is, that i changed the width and height properties of the Image before creating the pattern, but that doesn't actually remap the image (and that is also no problem). The thing is, that I now have got a pattern, that is of a different size (the original image size) than the image itself. If I want to draw a line with the fillStyle of that pattern, it doesn't fit (because I need a pattern of the new size).
My question: Is there an easy way, to achieve that the pattern's width and height can be adjusted?
My tries and why I dont like them:
1) Render the original image to a canvas with the new size and create the pattern from that. Didn't use this, because the pattern cannot be loaded directly as a result of the canvas being created and rendered to too slowly. But I want that pattern directly
2) Calculate the variance between the new image size and the original one, change the lineWidth of the context, so the patterns height fits exactly and scale the line down, so it has a nice size. Didn't use that because I render in realtime and this is way too slow to be used later in webapps.
Using canvas (your step 1) is the most flexible way.
It's not slower using a canvas to draw on another canvas than using an image directly. They both use the same element basis (you're blitting a bitmap just as you do with an image).
(Update: Drawing using pattern as style do go through an extra step of a local transformation matrix for the pattern in more recent browsers.)
Create a new canvas in the size of the pattern and simple draw the image into it:
patternCtx.drawImage(img, 0, 0, patternWidth, patternHeight);
Then use the canvas of patternCtx as basis for the pattern (internally the pattern caches this image the first time it's drawn, from there, if possible, it just doubles out what it has until the whole canvas is filled).
The other option is to pre-scale the images to all the sizes you need them to be, load them all in, and then choose the image which size is the one you need.
The third is to draw the image yourself as a pattern. This however is not so efficient compared to the built-in method, though using the above mentioned method (internal) you can get a usable result.
Example of manual patterning:
var ctx = canvas.getContext('2d');
var img = new Image();
img.onload = function() {
fillPattern(this, 64, 64);
change.onchange = change.oninput = function() {
fillPattern(img, this.value, this.value);
}
};
img.src = "//i.stack.imgur.com/tkBVh.png";
// Fills canvas with image as pattern at size w,h
function fillPattern(img, w, h) {
//draw once
ctx.drawImage(img, 0, 0, w, h);
while (w < canvas.width) {
ctx.drawImage(canvas, w, 0);
w <<= 1; // shift left 1 = *2 but slightly faster
}
while (h < canvas.height) {
ctx.drawImage(canvas, 0, h);
h <<= 1;
}
}
<input id=change type=range min=8 max=120 value=64><br>
<canvas id=canvas width=500 height=400></canvas>
(or with a video as pattern).

Categories