Hit testing pseudo 3d space - html

So I am writing a little mini pseudo 3d engine for the canvas element in html5. In the code below I am drawing a bunch of squares with varying positions and rotations (rotation around the z axis, so no deformation)
Now I want to be able to tell which square the user clicks on. In the objects array the items are supported by the z position starting with the squares the furthest away from the camera (so that they draw properly). So given a 3d point relative to the top left of the corner of the canvas how can I tell which square was clicked?
//Draw objects
for (var i = 0; i < objects.length; i++) {
var object = objects[i];
var cz = object.position.z - camera.position.z;
if (cz > 0) {
cz = 1 / ((cz - 1) * 0.75 + 1);
context.save();
context.translate(halfWidth, halfHeight); //viewport transform
context.scale(1, -1); //invert y axis
context.scale(cz, cz); //perspective
context.translate(-camera.position.x, -camera.position.y); //camera transform
context.translate(object.position.x, object.position.y); //world transform
context.rotate(object.rotation);
context.fillStyle = object.color;
context.fillRect(-40, -40, 80, 80);
context.restore();
}
}
P.S. If I am doing anything weird or backwards and you know of a way to improve, I would love to hear suggestions

I would suggest that you draw the objects with the same transformations to a hidden canvas of the same size, but give each square a unique color (maybe derived from the index i).
You would do that like this:
var col = index.toString(16); // convert to hex
while (col.length < 6) col = "0"+col; // pad leading 0s
ctx.fillStyle = "#"+col;
ctx.fillRect(-40,-40,80,80);
Then when you get a mouseclick event on the visible canvas, look at that location in your hidden one to get the color (index) of the selected object:
var colData = ctx.getImageData(clickX, clickY, 1, 1).data;
var index = (colData[2]<<16) | (colData[1]<<8) | colData[0];
This will work for up to 16M objects and is fairly simple.

Related

How to fill custom shape has been draw in Canvas?

Im try to drew custom shape, but since I use moveTo ..I cant be filled it, so That my question is there's any way can may be determined points on screen to fill shape? or to do that I most use or drew another real shape in the same block as layer ...
Look at my example here to drew a simple shape:
to I can fill image with blue color Im drew a Fill rectangle, so That is is a true way?
Code for shape for before fill:
var canvas3 = document.getElementById('canvas3');
var c3 = canvas3.getContext('2d');
c3.fillStyle = 'green';
c3.beginPath();
c3.moveTo(10,30);
c3.lineTo(200,30);
c3.moveTo(10,80);
c3.lineTo(200,80);
c3.moveTo(10,30);
c3.lineTo(10,180);
c3.moveTo(200,30);
c3.lineTo(200,180);
//c3.closePath();
c3.fill();
c3.lineWidth = 5;
c3.strokeStyle = 'orange';
c3.stroke();
Code for shape after fill:
var canvas3 = document.getElementById('canvas3');
var c3 = canvas3.getContext('2d');
c3.fillStyle = 'blue';
c3.beginPath();
c3.moveTo(10,30);
c3.fillRect(10,30,190,60);
c3.moveTo(10,30);
c3.lineTo(10,180);
c3.moveTo(10,90);
c3.lineTo(200,90);
c3.moveTo(200,30);
c3.lineTo(200,180);
c3.moveTo(10,30);
c3.lineTo(200,30);
//c3.closePath();
c3.fill();
c3.lineWidth = 5;
c3.strokeStyle = 'orange';
c3.stroke();
and finally which is a best way to I can drew shapes like this?
Note: Im new on html5 canvas and I read from this online book.
is there any way can may be determined points on screen to fill
shape? or to do that I most use or drew another real shape in the same
block as layer
Just draw a shape in the same place. Fill first then stroke afterwards. A little planning may be required with canvas as to in which order to draw things.
You can define objects to hold the geometrical data if you plan to redraw often or move them around. This will certainly simplify the objective later on.
which is a best way to I can drew shapes like this?
In my opinion this code can be drawn much simpler and in fewer lines of codes. There is no need to break up a shape in several parts as in that code if you can draw a shape using a simple method for it. In this case four lines can be replaced with one rectangle.
Knowing how these shapes are drawn internally also helps so we can take advantage of the path a rect() leaves, i.e. closing in upper-left corner so we can continue from there.
For example:
var c3 = c.getContext("2d");
// fill blue box first as it will be at the bottom
c3.rect(10, 30, 190, 50); // x, y, width, height
c3.fillStyle = 'blue';
c3.fill();
// orange stroke
// we know rect() will close at upper-left corner so continue from there with left leg
c3.lineTo(10, 180);
c3.moveTo(200, 80); // create second leg at right separately
c3.lineTo(200, 180);
c3.strokeStyle = "orange";
c3.lineWidth = 5;
c3.lineJoin = c3.lineCap = "round";
c3.stroke();
<canvas id=c height=180></canvas>
An alternative approach would be to fill then build the line path:
var c3 = c.getContext("2d");
c3.fillStyle = 'blue';
c3.fillRect(10, 30, 190, 50); // fillRect does not add to path
// orange stroke
c3.moveTo(10, 180); // create "housing" starting at bottom-left corner
c3.lineTo(10, 30); // upper-left
c3.lineTo(200, 30); // upper-right
c3.lineTo(200, 180); // bottom-right
c3.moveTo(10, 80); // add cross-bar
c3.lineTo(200, 80);
c3.strokeStyle = "orange";
c3.lineWidth = 5;
c3.lineJoin = c3.lineCap = "round";
c3.stroke();
<canvas id=c height=180></canvas>

HTML5: Inverse text-color on canvas

I want to draw text on a canvas in the inverse color of the background (to make sure the text is readible no matter the background color). I believe in oldskool bitblt-ing, this was an XOR operation.
How to do this?
Update: most of the newer browsers now support the blending mode "difference" which can achieve the same result.
context.globalCompositeOperation = "difference";
Updated demo.
Old answer:
One should think that the XOR mode for composition would do this, but unfortunately canvas' XOR only XORs the alpha bits.
By applying the following code we can however receive a result such as this:
You can make an extension to the canvas like this:
CanvasRenderingContext2D.prototype.fillInversedText =
function(txt, x, y) {
//code - see below
}
Now you can call it on the context as the normal fillText, but with a slight change:
ctx.fillInversedText(txt, x, y);
For this to work we do the following first - measure text. Currently we can only calculate width of text and then assume the height. This may or may not work well as fonts can be very tall and so forth. Luckily this will change in the future, but for now:
var tw = this.measureText(txt).width;
var th = parseInt(ctx.font, '10');
th = (th === 0) ? 10 : th; //assume default if no font and size is set
Next thing we need to do is to setup an off-screen canvas to draw the text we want ot invert:
var co = document.createElement('canvas');
co.width = tw;
co.height = th;
Then draw the actual text. Color does not matter as we are only interested in the alpha channel for this canvas:
var octx = co.getContext('2d');
octx.font = this.font;
octx.textBaseline = 'top';
octx.fillText(txt, 0, 0);
Then we extract the pixel buffers for the area we want to draw the inverted text as well as all the pixels for the off-screen canvas which now contains our text:
var ddata = this.getImageData(x, y, tw, th);
var sdata = octx.getImageData(0, 0, tw, th);
var dd = ddata.data; //cache for increased speed
var ds = sdata.data;
var len = ds.length;
And then we invert each pixel where alpha channel for pixel is greater than 0.
for (var i = 0; i < len; i += 4) {
if (ds[i + 3] > 0) {
dd[i] = 255 - dd[i];
dd[i + 1] = 255 - dd[i + 1];
dd[i + 2] = 255 - dd[i + 2];
}
}
Finally put back the inverted image:
this.putImageData(ddata, x, y);
This may seem as a lot of operations, but it goes pretty fast.
Demo (warning if you are sensitive to flicker)
(the psychedelic background is just to have some variations as fiddle needs external images and most are prevented by CORS when we use pixel manipulation).
I've removed my old answer, as it did not solve the question. As of recently, there are new globalCompositeOperations that do all kinds of great things. I've created an example that shows how to obtain inverted text. In case that link breaks, the method is essentially this:
ctx.globalCompositeOperation = "difference";
ctx.fillStyle = "white";
//draw inverted things here
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation

as3 wipe fade alpha using tween

Is it possible to fade the alpha with a soft wipe transition using as3 tween?
I thought maybe http://www.greensock.com might have the answer but I have found nothing. I would like the image to slowly fade away from one side to the other. A soft dissolve.
I thought maybe its possible using a mask but I don't think masks accept alphas otherwise it could be done that way.
Actually masks allow alphas. It's kind of a hack. You should try writing this in code:
maskMC.cacheAsBitmap = true;
objMC.cacheAsBitmap = true;
objMC.mask = maskMC;
Where objMC is your animated MovieClip and maskMC is your Mask that contains a gradient shape with transparency. See an example here: Link
You can also achieve this effect using Greensock. Code would look like this:
TweenLite.to(objMC, 1, {"alpha":0, "x":objMC.x + 10});
When using TweenLite, you need to provide object to animate, duration of animation and an instance of an Object class (that's stuff we write between curly braces). This instance contains all the values we want to change gradually.
You can accomplish this by using the ALPHA blendmode.
Make a shape that has a gradient whose alpha goes from full to nothing, then make it's blend mode ALPHA, put it in the same container as your item you wish to mask.
Then set the container blendmode to LAYER
Psuedo Code:
container.blendMode = BlendMode.LAYER; //container holds both the mask and the thing you want masked
maskObj.blendMode = BlendMode.ALPHA;
drawMaskGradients();
Here is a function I've used in the past to create said mask via code: (itemContainer is the object I'm masking)
You could however do this all in the Flash IDE using design tools.
softMaskSprite = new Sprite();
this.blendMode = BlendMode.LAYER;
softMaskSprite.blendMode = BlendMode.ALPHA;
softMaskSprite.mouseChildren = false;
softMaskSprite.mouseEnabled = false;
this.addChildAt(softMaskSprite,this.getChildIndex(itemContainer)+1);
//Create Sides
var top:Shape = new Shape();
var matr:Matrix = new Matrix();
matr.createGradientBox(areaWidth + (softMaskWidth * 2), softMaskWidth, 90 * (Math.PI / 180), 0, 0);
top.graphics.beginGradientFill(GradientType.LINEAR, [0xFF0000, 0x0000FF], [0,1], [0,255], matr, SpreadMethod.PAD);
top.graphics.drawRect(0, 0, areaWidth + (softMaskWidth * 2), softMaskWidth);
top.graphics.endFill();
top.x = softMaskWidth * -1;
top.y = softMaskWidth * -1;
softMaskSprite.addChild(top);
//BOTTOM
var bottom:Shape = new Shape();
matr = new Matrix();
matr.createGradientBox(areaWidth + (softMaskWidth * 2), softMaskWidth, 90 * (Math.PI / 180), 0, 0);
bottom.graphics.beginGradientFill(GradientType.LINEAR, [0xFF0000, 0x0000FF], [1,0], [0,255], matr, SpreadMethod.PAD);
bottom.graphics.drawRect(0, 0, areaWidth + (softMaskWidth * 2), softMaskWidth);
bottom.graphics.endFill();
bottom.y = areaHeight;
bottom.x = softMaskWidth * -1;
softMaskSprite.addChild(bottom);

canvas isPointInPath does not work with ctx.drawImage()

I suppose this doesn't work because canvas is drawing a bitmap of a vector (and a bitmap is not a path).
Even if it did work, the bitmap is likely always has a rectangular permitter.
Is there any way to leverage something like isPointInPath when using drawImage?
example:
The top canvas is drawn using drawImage and isPointInPath does not work.
The bottom canvas is drawn using arc and isPointInPath works.
a link to my proof
** EDIT **
I draw a circle on one canvas, and use isPointInPath to see if the mouse pointer is inside the circle (bottom canvas in my example).
I also "copy" the bottom canvas to the top canvas using drawImage. Notice that isPointInPath will not work on the top canvas (most likely due to reasons I mentioned above). Is there a work-around I can use for this that will work for ANY kind of path (or bitmap)?
A canvas context has this hidden thing called the current path. ctx.beginPath, ctx.lineTo etc create this path.
When you call ctx.stroke() or ctx.fill() the canvas strokes or fills that path.
Even after it is stroked or filled, the path is still present in the context.
This path is the only thing that isPointInPath tests.
If you want to test if something is in an image you have drawn or a rectangle that was drawn with ctx.fillRect(), that is not possible using built in methods.
Typically you'd want to use a is-point-in-rectangle function that you write yourself (or get from someone else).
If you're looking for how to do pixel-perfect (instead of just the image rectangle) hit detection for an image there are various methods of doing that discussed here: Pixel perfect 2D mouse picking with Canvas
You could try reimplementing ctx.drawImage() to always draw a box behind the image itself, like so (JSFiddle example):
ctx.customDrawImage = function(image, x, y){
this.drawImage(image, x, y);
this.rect(x, y, image.width, image.height);
}
var img1 = new Image();
img1.onload = function(){
var x = y = 0;
ctx.drawImage(img1, x, y);
console.log(ctx.isPointInPath(x + 1, y + 1));
x = 1.25 * img1.width;
ctx.customDrawImage(img1, x, y);
console.log(ctx.isPointInPath(x + 1, y + 1));
Note: you might get side effects like the rectangle appearing over the image, or bleeding through from behind if you are not careful.
To me, isPointInPath failed after canvas was moved. So, I used:
mouseClientX -= gCanvasElement.offsetLeft;
mouseclientY -= gCanvasElement.offsetTop;
I had some more challenges, because my canvas element could be rescaled. So first when I draw the figures, in my case arc, I save them in an array together with a name and draw them:
if (this.coInit == false)
{
let co = new TempCO ();
co.name= sensor.Name;
co.path = new Path2D();
co.path.arc(c.X, c.Y, this.radius, 0, 2 * Math.PI);
this.coWithPath.push(co);
}
let coWP = this.coWithPath.find(c=>c.name == sensor.Name);
this.ctx.fillStyle = color;
this.ctx.fill(coWP.path);
Then in the mouse event, I loop over the items and check if the click event is in a path. But I also need to rescale the mouse coordinates according to the resized canvas:
getCursorPosition(event) {
const rect = this.ctx.canvas.getBoundingClientRect();
const x = ((event.clientX - rect.left ) / rect.width) * this.canvasWidth;
const y = ((event.clientY - rect.top) / rect.height) * this.canvasHeight;
this.coWithPath.forEach(c=>{
if (this.ctx.isPointInPath(c.path, x, y))
{
console.log("arc is hit", c);
//Switch light
}
});
}
So I get the current size of the canvas and rescale the point to the original size. Now it works!
This is how the TempCO looks like:
export class TempCO
{
path : Path2D;
name : string;
}

How to draw segment of a donut with HTML5 canvas?

As the title states. Is this possible?
Edit: When i say doughnut I mean a top, 2D view
Is the only option to draw a segment of a circle, then draw a segment of a smaller circle with the same origin and smaller radius over the top, with the colour of the background? That would be crap if so :(
You do it by making a single path with two arcs.
You draw one circle clockwise, then draw a second circle going counter-clockwise. I won't go into the detail of it, but the way paths are constructed knows to take this as a reason to un-fill that part of the path. For more detail of what its doing you can this wiki article.
The same would work if you were drawing a "framed" rectangle. You draw a box one way (clockwise), then draw the inner box the other way (counter-clockwise) to get the effect.
Here's the code for a doughnut:
var can = document.getElementById('canvas1');
var ctx = can.getContext('2d');
// Pay attention to my last argument!
//ctx.arc(x,y,radius,startAngle,endAngle, anticlockwise);
ctx.beginPath()
ctx.arc(100,100,100,0,Math.PI*2, false); // outer (filled)
ctx.arc(100,100,55,0,Math.PI*2, true); // inner (unfills it)
ctx.fill();
Example:
http://jsfiddle.net/Hnw6a/
Drawing only a "segment" of it can be done by making the path smaller (you might need to use beziers instead of arc), or by using a clipping region. It really depends on how exactly you want a "segment"
Here's one example: http://jsfiddle.net/Hnw6a/8/
// half doughnut
ctx.beginPath()
ctx.arc(100,100,100,0,Math.PI, false); // outer (filled)
ctx.arc(100,100,55,Math.PI,Math.PI*2, true); // outer (unfills it)
ctx.fill();
You can make a 'top view doughnut' (circle with hollow center) by stroking an arc. You can see an example of this here: http://phrogz.net/tmp/connections.html
The circles (with nib) are drawn by lines 239-245:
ctx.lineWidth = half*0.2; // set a nice fat line width
var r = half*0.65; // calculate the radius
ctx.arc(0,0,r,0,Math.PI*2,false); // create the circle part of the path
// ... some commands for the nib
ctx.stroke(); // actually draw the path
Yes, I understand how old this question is :)
Here are my two cents:
(function(){
var annulus = function(centerX, centerY,
innerRadius, outerRadius,
startAngle, endAngle,
anticlockwise) {
var th1 = startAngle*Math.PI/180;
var th2 = endAngle*Math.PI/180;
var startOfOuterArcX = outerRadius*Math.cos(th2) + centerX;
var startOfOuterArcY = outerRadius*Math.sin(th2) + centerY;
this.beginPath();
this.arc(centerX, centerY, innerRadius, th1, th2, anticlockwise);
this.lineTo(startOfOuterArcX, startOfOuterArcY);
this.arc(centerX, centerY, outerRadius, th2, th1, !anticlockwise);
this.closePath();
}
CanvasRenderingContext2D.prototype.annulus = annulus;
})();
Which will add a function "annulus()" similar to "arc()" in the CanvasRenderingContext2D prototype. Making the closed path comes in handy if you want to check for point inclusion.
With this function, you could do something like:
var canvas = document.getElementById("canvas1");
var ctx = canvas.getContext("2d");
ctx.annulus(0, 0, 100, 200, 15, 45);
ctx.fill();
Or check this out: https://jsfiddle.net/rj2r0k1z/10/
Thanks!
With WebGL (one of the contexts of the HTML5 canvas) that is possible. There are even some JS libraries for browsers that don't support/implement it yet - check out these links:
http://sixrevisions.com/web-development/how-to-create-an-html5-3d-engine/
http://slides.html5rocks.com/#landing-slide
http://sebleedelisle.com/2009/09/simple-3d-in-html5-canvas/
http://www.khronos.org/webgl/
http://webdesign.about.com/od/html5tutorials/f/is-there-a-3d-context-for-html5-canvas.htm
http://code.google.com/p/html-gl/
Given the requirements, what #SimonSarris says satisfies the problem. But lets say you're like me and you instead want to "clear" a part of a shape that may be partially outside the bounds of the shape you're clearing. If you have that requirement, his solution won't get you want you want. It'll look like the "xor" in the image below.
The solution is to use context.globalCompositeOperation = 'destination-out' The blue is the first shape and the red is the second shape. As you can see, destination-out removes the section from the first shape. Here's some example code:
explosionCanvasCtx.fillStyle = "red"
drawCircle(explosionCanvasCtx, projectile.radius, projectile.radius, projectile.radius)
explosionCanvasCtx.fill()
explosionCanvasCtx.globalCompositeOperation = 'destination-out' #see https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
drawCircle(explosionCanvasCtx, projectile.radius + 20, projectile.radius, projectile.radius)
explosionCanvasCtx.fill()
Here's the potential problem with this: The second fill() will clear everything underneath it, including the background. Sometimes you'll want to only clear the first shape but you still want to see the layers that are underneath it.
The solution to that is to draw this on a temporary canvas and then drawImage to draw the temporary canvas onto your main canvas. The code will look like this:
diameter = projectile.radius * 2
console.log "<canvas width='" + diameter + "' height='" + diameter + "'></canvas>"
explosionCanvas = $("<canvas width='" + diameter + "' height='" + diameter + "'></canvas>")
explosionCanvasCtx = explosionCanvas[0].getContext("2d")
explosionCanvasCtx.fillStyle = "red"
drawCircle(explosionCanvasCtx, projectile.radius, projectile.radius, projectile.radius)
explosionCanvasCtx.fill()
explosionCanvasCtx.globalCompositeOperation = 'destination-out' #see https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
durationPercent = (projectile.startDuration - projectile.duration) / projectile.startDuration
drawCircle(explosionCanvasCtx, projectile.radius + 20, projectile.radius, projectile.radius)
explosionCanvasCtx.fill()
explosionCanvasCtx.globalCompositeOperation = 'source-over' #see https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
ctx.drawImage(explosionCanvas[0], projectile.pos.x - projectile.radius, projectile.pos.y - projectile.radius) #center
Adapting/simplifying #Simon Sarris's answer to easily work with any angle gives the below:
To create an arc segment you draw an outer arc (of n radians) in one direction and then an opposite arc (of the same number of radians) at a smaller radius and fill in the resulting area.
var can = document.getElementById('canvas1');
var ctx = can.getContext('2d');
var angle = (Math.PI*2)/8;
var outer_arc_radius = 100;
var inner_arc_radius = 50.;
ctx.beginPath()
//ctx.arc(x,y,radius,startAngle,endAngle, anticlockwise);
ctx.arc(100,100,outer_arc_radius,0,angle, false); // outer (filled)
// the tip of the "pen is now at 0,100
ctx.arc(100,100,inner_arc_radius,angle,0, true); // outer (unfills it)
ctx.fill();
<canvas id="canvas1" width="200" height="200"></canvas>