I am having an issue trying to update a drawing that uses a number of different drawing objects. The drawing is similar to an AutoCad drawing and is measured in mm so the scale is already being calculated in order to get the drawing to fit on the stage. When this is calculated the scale is set to one.
I have the drawing objects (lines, circles, arcs, etc.) stored in an array. I am trying to update the drawing andd rescale it without clearing the stage and doing a full redraw to improve performance.
What I am trying to do is to increment the length of the drawing. The drawing has a cut point so the objects to the right of the cut point will move by the increment value and any lines that span the cut point will increase in length. This change will require an update to the scale initially calculated to get the drawing to appear on the stage.
There are 2 Fiddles that I have set up to demonstrate the problem. The first (http://jsfiddle.net/tctruckscience/3HxuP/4/) shows what I am currently doing. The problem is that drawing will scale but it will start to move away from the right hand side of the screen.
originalRectWidth = 2600;
rectWidth = rectWidth + 20;
scaleValue = originalRectWidth / rectWidth;
oldRectWidth = rect.getWidth();
newPixelsPerScaleUnit = 260 / rectWidth;
newRectScaled = rectWidth * newPixelsPerScaleUnit;
drawingGroup.setWidth(newRectScaled);
rect.setWidth(newRectScaled);
drawingGroup.scaleBy(scaleValue);
Also, if I make a lot of increments and then make a large decrement using a text box there is an issue in the redraw. It that lines are not decremented correctly. I think it is an issue with the scaling. When I resize the page, which calls a refresh of the drawing objects from the array in which they are held, the drawing proportions are correct
The second Fiddle (http://jsfiddle.net/tctruckscience/rsEyA/15) shows how I would like the drawing to behave.
oldWidth = drawingGroup.getWidth();
newWidth = drawingGroup.getWidth() + 20;
scaleValue = originalWidth / newWidth;
Is there a way to do this?
Related
Can someone help me understand how three.js initially determines the size/scale of a sprite?
At the moment I'm working with 4 sprites (PNGs with transparency that are 3000px × 1830px) stacked in 3D space, but I'm having to scale them up between 16x and 22x. In order to keep the sprites from looking squashed, though, I have to scale the y-axis 75% of the x-scale.
Eventually, I want to be able to pull in images systematically, and have them scale appropriately.
It's possible I just haven't set this thing up correctly. It looks right, but it's super hacky-feeling right now. I pretty much changed a bunch of numbers, until it looked right to me. I don't like that. I want to understand.
Here's what I'm working with currently:
http://valorink.com/3d-test/stackoverflow/
Looking into the code of the Sprite class reveals that a simple plane with width and height of 1 is created in the constructor. I wouldn't have expected anything else, because the geometry size is usually not defined by the texture size.
You probably want them to fill the viewport, so you have to scale them. With perspective camera its a bit of math, because the amount of x-scale (or y-scale) to fit the viewport size relates to the distance to the camera. So, it should be something like
var halfHeigt = distanceToCamera / Math.tan( camera.fov/2 * Math.PI / 180 );
y-scale = halfHeight * 2;
And of course you need to consider the aspect ratio in order to not looking squashed. So, x-scale should be y-scale * textureWidth / textureHeight, or the other way round y-scale = x-scale * textureHeight / textureWidth.
This question already has answers here:
HTML5 Canvas camera/viewport - how to actually do it?
(7 answers)
Closed 7 years ago.
Creating an Agar clone, and it pretty much works now, but I want to improve it by making it so if the player leaves the screen, the screen follows them, which is done with a viewport. Only problem is I have no idea how to do it. I tried ctx.translate() but that just resulted in some weird stuff. (granted, I'm using a regular background, and I've heard an actual image is required for it to work, but I don't know how to do that either, so...) Here's my jsFiddle. Initializing canvas-related variables:
var canvas = document.createElement("canvas");
canvas.width = innerWidth;
canvas.height = innerHeight;
canvas.style.display = "none";
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
You use translate to set the origin. Where on the canvas, or off, the coordinates X = 0, y = 0 are.
Your character is wandering about a game world which is many times bigger than the canvas. You also have many other items and players each with their own coordinates in the game world..
One way to render this is... For each item in the game, find the distance you are from the origin and subtract that from each item as you draw them. Then draw your own character at the center of the screen. But that is slow, and requires all that extra math for each item/player.
So let the hardware do it with the transform. The transform holds the scales (x,y) the rotation (ang), and the translation (origin offset). Every time an object is drawn on the canvas the transformation matrices is applied to it, you can't avoid it, it has to happen. So if you dont use it to your advantage its just wasted.
How to use it.
Your playfield is top left -10,000,-10,000 pixels to bottom right 10,000, 10,000 pixels. Every one has a location on the map between those numbers, including your player, which may be at 5,000, 6,000 pixels. If you ctx.drawImage(playerImage,5000,6000) the image will not appear, its way of the bottom right of the canvas, but you want him in the center of the screen.
To do this move the origin (currently at the upper left corner of the canvas (0,0)) so you at 5000,6000 appear in the center.
var playerX = 5000; // your position
var playerY = 6000;
var screenWidth = 1920; // size of the screen
var screenHeight = 1080;
var originX = 0; // where the origin is
ver originY = 0
To center yourself move the origin so that it subtracts you position.
originX -= playerX;
originY -= playerY;
now you are at the top left corner of the canvas, but you want the center. So move it back a bit by half the screen size.
originX += screenWidth/2;
originY += screenHeight/2;
Combine it all into one. Assume the origin is alway 0,0 and you get
originX = -(playerX-screenWidth/2);
originY = -(playerY-screenHeight/2);
Now you have the numbers that you can put into the translate.
But its much better if you put it straight into the transformation matrix, and you don't really need the origin variables.
Thus in one step.
ctx.setTransform(1,0,0,1,-(playerX-screenWidth/2),-(playerY-screenHeight/2));
Now when you draw your player at
ctx.drawImage(payerImage,5000,6000);
You appear at the center of the canvas. The bad guy at 5500,6200 draw with
ctx.drawImage(enemyImage,5500,6200);
will appear on the canvas as well 500 pixels right and 200 down from you.
Now you don't have to mess around with anyones coordinates and just render them where they are, you have moved everything in the game with only one line of code. It has not slowed your game down at all, because the transform is always applied, you just made sure its doing what you want.
Set the transform near the start of every frame, after you have updated your position and everything will follow your character.
I am working on a big project where exercises in Canvas are created through JSON-data and CreateJS. The purpose of having it in HTML 5 is to not have to use a separate app for your phone, you can always use the website.
Everything works fine, however in mobile the Canvas is rescaled to full screen. This is done through checking the screen size, and if it's small enough to be mobile the canvas is scaled through this code:
// browser viewport size
var w = window.innerWidth;
var h = window.innerHeight;
// stage dimensions
var ow = canvasWidth;
var oh = canvasHeight;
// keep aspect ratio
var scale = Math.min(w / ow, h / oh);
stage.scaleX = scale;
stage.scaleY = scale;
// adjust canvas size
stage.canvas.width = ow * scale;
stage.canvas.height = oh * scale;
This works great for most of the exercises, like quizzes and such, where all you have to do is click on a button. However we also have some drag and drop-exercises, and an exercise where you can color a drawing. These of course rely on the mouse coordinates to work properly. The problem is, when the canvas is scaled the mouse coordinates are not. So when you drag an item or try to draw, there is an offset happening. So your drawing appears way left of your click, and when picking up a draggable object it doesn't quite follow your click correctly.
Had I made the code from the beginning I'm fairly sure how I would have recalculated the coordinates, but since they are calculated by CreateJS I don't really know how I should go about this.
This was reported as a problem by someone about a year ago here, where this solution was suggested:
I was able to work around this by adding a top-level container and attaching my Bitmaps to that and scaling it.
The whole exercise is inside a container which I have tried to scale but to no avail. I have also tried sending the scale as a parameter to the parts of the exercise created (for example the menu, background images and such) and not scale it all together, and it seems to work okay since then I can exclude the drawing layer. But since it is a large project and many different exercises and parts to be scaled it would take quite some time to implement, and I'm not sure it's a viable solution.
Is there a good and easy way to rescale the mouse coordinates along with the canvas size in CreateJS? I have found pure Javascript examples here on SO, but nothing for CreateJS in particular.
Continued searching and finally stumbled upon this, which I hadn't seen before:
EaselJS - dragging children of scaled parent. It was exactly what I was looking for. I needed to change the coordinates I drew with this:
var coords = e.target.globalToLocal(e.stageX, e.stageY);
Then I could use the coords.x and coords.y instead of directly using e.stageX and e.stageY like before.
I'm writing a 2D game in html5 using Canvas which requires mouse click and hover events to be detected. There are 3 problems with this: detections must be pixel-perfect, objects are not rectangular (houses, weird-shaped UI buttons...), and it is required to be fast and responsive. (Obviously brute force is not an option)
So what I want to ask is how do I find out which object the mouse is on, and what are the possible optimizations.
P.S: I did some investigation and found a guy who used QuadTree here.
I have a (dated) tutorial that explains the concept of a ghost canvas which is decent for pixel-perfect hit detection. The tutorial is here. Ignore the warning about a newer tutorial, the newer one does not use the ghost canvas concept.
The idea is to draw the image in question to an in-memory canvas and then use getImageData to get the single pixel of the mouse click. Then you see if that single pixel is fully transparent or not.
If its not fully transparent, well, you've got your target.
If it is fully transparent, draw the next object to the in-memory canvas and repeat.
You only have to clear the in-memory canvas at the end.
getImageData is slow but it is your only option if you want pixel-perfect hit detection and aren't pre-computing anything.
Alternatively you could precompute a path or else an array of pixels with an offset. This would be a lot of work but might be faster. For instance if you have a 40x20 image with some transparency you'd compute an array[40][20] that would have true or false corresponding to transparent or not. Then you'd test that against the mouse position, with some offset, if the image is drawn at (25, 55) you'd want to subtract that from the mouse position and then test if the new position is true when you look at array[posx][posy].
That's my answer to your question. My Suggestion? Forget pixel-perfect detection if this is a game.
Seriously.
Instead make paths (not in canvas, in plain javascript code) that represent the objects but are not pixel perfect, for instance a house might be a square with a triangle on the top that is a very close approximation of the image but is used in its stead when it comes to hit testing. It is comparatively extremely fast to compute if a point is inside a path than it is to do pixel-perfect detection. Look up point in polygon winding number rule detection. That's your best bet, honestly.
The common solution in traditional game development is to build a click mask. You can re-render everything onto a separate off-screen canvas in a solid color (the rendering should be very quick). When you want to figure out what was clicked on, you simply sample the color at the x/y co-ordinate on the off-screen canvas. You end up building a color-->obj hash, akin to:
var map = {
'#000000' : obj1
, '#000001' : obj2
, ...
};
You can also optimize the rendering to the secondary canvas to only happen when the user clicks on something. And using various techniques, you can further optimize it to only draw the part of the canvas that the user has clicked on (for example, you can split you canvas into an NxN grid, e.g. a grid of 20x20 pixel squares, and flag all of the objects in that square -- you'd then only need to re-draw a small number of objects)
HTML5 Canvas is just a drawing plane, where you can set different transforms before calling each drawing API function. Objects cannot be created and there is no display list. So you have to build these features yourself or you can use different libraries available for this.
http://www.kineticjs.com/
http://easeljs.com/
A few months before I got interested in this and even wrote a library for this purpose. You can see it here : http://exsprite.com. Ended up facing a lot of performance issues, but because of lack of time I couldn't optimize it. It was really interesting, so waiting for some time to make it perfect.
I believe the comments should suffice. This is how I determine user intention in my 2d isometric scroller, currently located at http://untitled.servegame.com
var lastUp = 0;
function mouseUp(){
mousedown = false; //one of my program globals.
var timeNow = new Date().getTime();
if(mouseX == xmouse && mouseY == ymouse && timeNow > lastUp + 100){//if it was a centralized click. (mouseX = click down point, xmouse = mouse's most recent x) and is at least 1/10th of a second after the previous click.
lastUp = new Date().getTime();
var elem = document.elementFromPoint(mouseX, mouseY); //get the element under the mouse.
var url = extractUrl($(elem).css('background-image')); // function I found here: http://webdevel.blogspot.com/2009/07/jquery-quick-tip-extract-css-background.html
imgW = $("#hiddenCanvas").width(); //EVERY art file is 88px wide. thus my canvas element is set to 88px wide.
imgH = $(elem).css('height').split('p')[0]; //But they vary in height. (currently up to 200);
hiddenCanvas.clearRect(0, 0, imgW, imgH); //so only clear what is necessary.
var img = new Image();
img.src = url;
img.onload = function(){
//draw this elements image to the canvas at 0,0
hiddenCanvas.drawImage(img,0,0);
///This computes where the mouse is clicking the element.
var left = $(elem).css('left').split('p')[0]; //get this element's css absolute left.
var top = $(elem).css('top').split('p')[0];
offX = left - offsetLeft; //left minus the game rendering element's absolute left. gives us the element's position relative of document 0,0
offY = top - offsetTop;
offX = mouseX - offX; //apply the difference of the click point's x and y
offY = mouseY - offY;
var imgPixel = hiddenCanvas.getImageData(offX, offY, 1, 1); //Grab that pixel. Start at it's relative X and it's relative Y and only grab one pixel.
var opacity = imgPixel.data[3]; //get the opacity value of this pixel.
if(opacity == 0){//if that pixel is fully transparent
$(elem).hide();
var temp = document.elementFromPoint(mouseX, mouseY); //set the element right under this one
$(elem).show();
elem = temp;
}
//draw a circle on our hiddenCanvas so when it's not hidden we can see it working!
hiddenCanvas.beginPath();
hiddenCanvas.arc(offX, offY, 10, 0, Math.PI*2, true);
hiddenCanvas.closePath();
hiddenCanvas.fill();
$(elem).css("top", "+=1"); //apply something to the final element.
}
}
}
In conjunction with this:
<canvas id="hiddenCanvas" width="88" height="200"></canvas>
Set the CSS positioning absolute and x = -(width) to hide;
Quick question involving javascript canvas... I have a set points (connected with a line) I want to graph on a 400x300 canvas element. I will constantly be adding more points. I need the line to stretch to fill the entire canvas (leaving no unnecessary space).
Example:
into this:
Thanks! C.Ruhl
You want to find the step by doing canvasWidth / (number of points - 1)
and adding X += step each time.
Example here:
http://jsfiddle.net/pDDTQ/
Distinguish between internal canvas size and visible size. 400x300 is your visible size and set by style="width:400px; height:300px". Everytime there is new point (e.g. 400,500) you set canvas.width=400; canvas.height=500; and replot the whole graph. From a certain point you might want to adjust the width of the line.