I want to get actual height of element when rotated 3d on x axis. I'll try to explain with graphics below:
Element's normal height: 200px
Actual height is 118px when rotate 45 degree with 100px perpective:
Actual height is 138px when rotate 45 degree with 1000px perpective:
Normal formule for calculate this height value (without perpective):
x = h * sin(angle)
Height must 142px with this formule. But it's diffrent from this value. Probably perspective changes height. But I don't find any formule for calculate this height.
Does anyone have any idea?
I benefited from efe's comment and calculated real view height of rotated element.
There is technical explanation about solution on djjeck's answer.
You can view this question's answer with an example: http://jsfiddle.net/TqJJL/3/
Calculate real height with this code:
// initial coordinates
var A = 0;
var B = width; // default size of element
// new coordinates
A = calc(A, angle*Math.PI/180, p);
B = calc(B, angle*Math.PI/180, p);
// translate back
A += width/2;
B += width/2;
if(B < A) { var tmp = A; A = B; B = tmp; } // swap
var realHeight = B-A;
function calc(oldx, angle, p) {
var x = Math.cos(angle) * oldx;
var z = Math.sin(angle) * oldx;
return x * p / (p+z);
}
Related
What I'm attempting to do
Loop through two axes and generating a shape with a width and height, either less or equal to the length of the nested for-loops, and calculate the distance from all positions to the center of that shape.
Main Issue(s)
How do I specify the width and height of an ellipse shape to draw using a nested for-loop with different dimensions to that ellipse?
For example a nested for-loop which goes for 0 to 45 in the X axis, and 0 to 100 in the Y axis but draws an ellipse with a width of 39 and a height of 90 - with the remaining difference used as padding (3 on either side, and 5 on top and bottom).
I have this half working using the EdgeOrInBounds function below, however I'm having trouble understanding why the values I'm using are giving the results they are.
Using a nested for-loop the same as above, but specifying an ellipse with a width of 30 and a height of 70 doesn't have the expected padding, it instead draws an ellipse with only one extra sprite surrounding all sides.
How do I calculate the distance from the center of the ellipse to the positions generated by the nested for-loop as a value between zero and one?
For example, any position outside the ellipse returns a value of zero and any position within the ellipse returns the distance scaled between zero and one from the center of the ellipse.
Similar to above, I have this half working as I can return a value of zero for all posiitons outside of the ellipse, but I do not understand how scale the distances for positions within the ellipse.
Bonus Issue(s)
I'm doing this on a platform where code isn't easily shareable and there are few built in functions, so I've had to create my own versions stolen from based on examples from the Nvidia developer site.
I have a basic understanding of some C# and JavaScript, but zero understanding of mathematical formulas.
Ellipse Function(s)
bool EdgeOrInBounds (Vector2 position) {
int x = ((int) Math.Pow (position.x - center.x, 2) / (int) Math.Pow (radius.x, 2));
int y = ((int) Math.Pow (position.y - center.y, 2) / (int) Math.Pow (radius.y, 2));
return (x + y <= 1);
}
Distance Function(s)
float distance (Vector2 position) {
return (sqrt (dot (centerPosition - position, centerPosition - position));
}
float dot (Vector2 a, Vector2 b) {
return (a.x * b.x + a.y * b.y);
}
float sqrt (float a) {
return (1.0 / pow (a, -0.5));
}
Variables
int mapWidth = 45;
int mapHeight = 100;
Vector2 radius = new Vector2 (mapWidth - 8, mapHeight - 4);
Vector2 center = new Vector2 (mapWidth / 2, mapHeight / 2);
Nested For Loops
for (int x = 0; x < width; x ++) {
for (int y = 0; y < height; y ++) {
// Store current position to reference in a minute
Vector2 position = new Vector2 (x, y);
// Check if position is within bounds or lies on the edge of the ellipse
if (EdgeOrInBounds (position)) {
// Calculate distance from center to current position
float dist = distance (position);
}
}
}
Example Image:
Closing Remarks
I know I haven't done a good job of explaining what I'm tring to achieve, so I'd like to apologize in advance, and I'd also like to thank anyone who reads this as any help would be very much appreciated.
Cheers.
To get color shade better under control, you could use an elliptic spiral, instead of a square grid traverse. Start out with the two radii, use X=R1 * Cos(angle) and Y=R2 * Sin(angle), where you gradually decrease R1 and R2 to zero. Your loop will use polar coordinates (angle,r), see below. You are then sure of the size of your "plot" and you won't need to test distances underways. It can probably do without any distance function for color scaling, but I'm not sure how to do that properly.. I have included a few options.
// The image is 440x240, I want ellipse in the center, margins 20 pix
// Parameters, dependent on size and shape of elllipse
Point pc = new Point(220,120); // pixel center
double r1=200; // radius 1 margin 2x20 on 440
double r2=100; // radius 2 margin 2x20 on 240
// Covering all pixels
int rmax = (int)Math.Max(r1,r2);
// scaling for color
var ravgmax = (r1+r2)/2.0;
// Find suitable loop counts
var nr = rmax; // number of radius steps in loop
var nh = 2*nr*Math.PI); // number of angles in loop
// Prepare initial loop displacements
var h=0.0;
var dr1 = r1/(nr*nh);
var dr2 = r2/(nr*nh);
var dh=(Math.PI*2.0)/nh;
// The loop
for (int i=0; i<nr; i++)
{
for (int j=0; j<(int)nh; j++)
{
var p = new PointF((float)(pc.X+r1*Math.Cos(h)),(float)(pc.Y+r2*Math.Sin(h)));
// vanilla shading
// int grayscale = 255 - (int)(255 * ((r1+r2)/2.0)/ravgmax );
// elliptical option without using distance, scale along axes
// grayscale = 255 - (int)(Math.Abs(p.X-pc.X)*255/200+Math.Abs((p.Y-pc.Y)*255/100)/2;
// "Distance grayscale" which is circular, not elliptical
int grayscale = (int)(255 * floatFDistance(p,pc)/rmax);
PlotF(p,grayscale); // you provide: plotpixel(PointF, int)
r1-=dr1; r2-=dr2;
h+=dh;
}
}
}
float floatFDistance(PointF p1, PointF p2)
{
double d1 = (p1.X - p2.X);
double d2 = (p1.Y - p2.Y);
return (float)(Math.Sqrt(d1 * d1 + d2 * d2));
}
So I've built a small graph application with JavaScript to help me practice using the canvas. I've spent the last 10 hours trying to scale between two points on the X-Axis and can't for the life of me figure it out. I've learned that to scale you need to translate > scale > translate. This works fine when I scale to the far left/right using the following type code.
let x = 0;
let y = this.getCanvasHeight() / 2;
this.getCanvasContext().clearRect(0, 0, this.getCanvas().width, this.getCanvas().height);
this.setCanvas();
ctx.translate(x, y);
ctx.scale(scale, 1);
ctx.translate(-x, -y);
this.resetCanvasLines();
this.renderGraph(this.state.points, scale);
This piece of code simply allows me to zoom into the far left of the graph. So now I'm trying to pick two points on this graph and zoom in on top of them, so that they fit evenly on the screen. The Y-Axis will always be the same.
My thinking was to get the midpoint between the two points and zoom in on that location, which I feel should work but I just can't get it working. My graph width is 3010px and split into 5 segments of 602px. I want to zoom let's say from x1 = 602 and x2 = 1806, which has the midpoint of 1204. Is there a technique to properly calculating the scale amount?
rangeFinder(from, to) {
let points = this.state.points;
if (points.length === 0) {
return;
}
let ctx = this.getCanvasContext();
let canvasWidth = this.getCanvasWidth();
let canvasHeight = this.getCanvasHeight() / 2;
let seconds = this.state.seconds;
let second = canvasWidth / seconds;
let scale = 1;
// My graph starts from zero, goes up to 5 and the values are to represent seconds.
// This gets the pixel value for the fromX value.
let fromX = from * second;
to = isNaN(to) ? 5 : to;
// Get the pixel value for the to location.
let toX = parseInt(to) * second;
let y = canvasHeight / 2;
// get the midpoint between the two points.
let midpoint = fromX + ((toX - fromX) / 2);
// This is where I really go wrong. I'm trying to calculate the scale amount
let zoom = canvasWidth - (toX - fromX);
let zoomPixel = (zoom / 10) / 1000;
let scaleAmount = scale + ((zoom / (canvasWidth / 100)) / 100) + zoomPixel;
ctx.clearRect(0, 0, this.getCanvas().width, this.getCanvas().height);
this.setCanvas();
// translate and scale.
ctx.translate(midpoint, y);
ctx.scale(scaleAmount, 1);
ctx.translate(-midpoint, -y);
this.resetCanvasLines();
this.renderGraph(points);
}
Any help would be great, thanks.
Scale = 5/3 = total width / part width.
After scale, x = 602 should have moved to 602 * 5/3 ~ 1000. Translate the new image by -1000. There is no need to find mid-point.
I would like to determine the proportion of a grid cell occupied by one (or more) circles. So, for example, the top left grid cell below would have a small value (~0.1) and the center grid cell (7,7) would have a value of 1, as it is entirely occupied by the circle.
At present I am doing this with canvas.context2d.getImageData, by sampling the cell's content to determine what is present. This works but is way too slow. This is this method:
var boxRadius = 6;
var boxSize = boxRadius * 2 + 1;
var cellWidth = gridWidth / boxSize;
var cellHeight = gridHeight / boxSize;
var scanInterval = 10;
var scanCount = 10;
for (var x = viewcenterpoint.x - (gridWidth / 2); x <= viewcenterpoint.x + (gridWidth / 2) -1; x += cellWidth) {
for (var y = viewcenterpoint.y - (gridHeight / 2) ; y <= viewcenterpoint.y + (gridHeight / 2) -1; y += cellHeight) {
var cellthreatlevel = 0.0;
for (var cellx = x; cellx < x + cellWidth; cellx += scanInterval){
for (var celly = y; celly < y + cellHeight; celly += scanInterval){
var pixeldata = context.getImageData(cellx, celly, 1, 1).data;
cellthreatlevel += ((pixeldata[0] + pixeldata[1] + pixeldata[2])/765 * -1) + 1;//255; //grey tone
scancount += 1;
}
}
cellthreatlevel = cellthreatlevel / scanCount; //mean
}
}
The getImageData call is the source of the problem - it is way too slow.
Given that I have an array of circles, each with their x, y and radius how can I calculate this? If possible I would like each value to be a decimal fraction (between 0 and 1).
The grid is static, but the circles may move within it. I would be happy to get a rough estimate for the value, it doesnt need to be 100% accurate.
You can use the Monte Carlo Method to get an approximate solution. It is a probability based method, in which you generate random samples in order to estimate some value. In this case, given the coordinates of the circle center, the circle radius and the boundaries of the grid cell, you can estimate the proportion of the grid cell occupied by the circle by generating K random samples (all contained inside the grid cell), and verify the proportion of the samples that are also inside the circle. The more samples you generate, the more accurate your result will be.
Remember: to verify if a given sample P is inside a circle with center C and radius R, all you have to do is check if the equation sqrt((Px-Cx)^2 + (Py-Cy)^2) <= R is true
You only need to call getImageData once, to obtain the entire canvas.
Once you have the image data you can access the bytes at offset 4 * (celly * width + cellx) to get the RGB(A) data.
This should be massively faster since it only makes one call to the graphics hardware instead of 10s of thousands.
Suppose my div has left:200px and top:400px, after I apply a rotate transform of suppose 90 deg the above top and left positions no more point to the old positions. Now how can we calculate the new top and left for the transformed div which are equivalent to the left and top positions of the non-transformed div after rotation.
Edited answer
Besides the starting position of the corner point (top-left in your example), and the rotation angle, we also need to know the position of the reference point of the rotation. This is the point around which we rotate the div (CSS calls it transform-origin). If you don't specify it, then normally, the centre of mass of the element is used.
I don't know of any JavaScript method that simply calculates it for you, but I can show you its Math, and a simple JS implementation.
Math
P: original position of the corner point, with (Px, Py) coordinates
O: reference point of the rotation, with (Ox, Oy) coordinates
Calculate the original position of P, relative to O.
x = Px - Ox
y = Py - Oy
Calculate the rotated position of P, relative to O.
x' = x * cos(angle) - y * sin(angle)
y' = x * sin(angle) + y * cos(angle)
Convert this position back to the original coordinate system.
Px' = x' + Ox
Py' = y' + Oy
If you're not aware of the formulas in step #2, you can find an explanation here.
JavaScript implementation
function rotatedPosition(pLeft, pTop, oLeft, oTop, angle){
// 1
var x = pLeft - oLeft;
var y = pTop - oTop;
// 2
var xRot = x * Math.cos(angle) - y * Math.sin(angle);
var yRot = x * Math.sin(angle) + y * Math.cos(angle);
// 3
var pLeftRot = xRot + oLeft;
var pTopRot = yRot + oTop
return {left: pLeftRot, top: pTopRot};
}
rotatedPosition requires you to define the original position of the point and the reference point, plus the angle.
In case you need a method which takes only a single argument, the div element itself, and computes the rest for you, then you can do something like:
function divTopLeftRotatedPosition(div){
var pLeft = // ...
var pTop = // ...
var width = // ...
var height = // ...
var angle = // ...
return rotatedPosition(pLeft, pTop, pLeft + width / 2, pTop + height / 2, angle);
}
I'm programming a HTML5 < canvas > project that involves zooming in and out of images using the scroll wheel.
I want to zoom towards the cursor like google maps does but I'm completely lost on how to calculate the movements.
What I have: image x and y (top-left corner); image width and height; cursor x and y relative to the center of the canvas.
In short, you want to translate() the canvas context by your offset, scale() it to zoom in or out, and then translate() back by the opposite of the mouse offset. Note that you need to transform the cursor position from screen space into the transformed canvas context.
ctx.translate(pt.x,pt.y);
ctx.scale(factor,factor);
ctx.translate(-pt.x,-pt.y);
Demo: http://phrogz.net/tmp/canvas_zoom_to_cursor.html
I've put up a full working example on my website for you to examine, supporting dragging, click to zoom in, shift-click to out, or scroll wheel up/down.
The only (current) issue is that Safari zooms too fast compared to Chrome or Firefox.
I hope, these JS libraries will help you:
(HTML5, JS)
Loupe
http://www.netzgesta.de/loupe/
CanvasZoom
https://github.com/akademy/CanvasZoom
Scroller
https://github.com/zynga/scroller
As for me, I'm using loupe. It's awesome!
For you the best case - scroller.
I recently needed to archive same results as Phrogz had already done but instead of using context.scale(), I calculated each object size based on ratio.
This is what I came up with. Logic behind it is very simple. Before scaling, I calculate point distance from edge in percentages and later adjust viewport to correct place.
It took me quite a while to come up with it, hope it saves someones time.
$(function () {
var canvas = $('canvas.main').get(0)
var canvasContext = canvas.getContext('2d')
var ratio = 1
var vpx = 0
var vpy = 0
var vpw = window.innerWidth
var vph = window.innerHeight
var orig_width = 4000
var orig_height = 4000
var width = 4000
var height = 4000
$(window).on('resize', function () {
$(canvas).prop({
width: window.innerWidth,
height: window.innerHeight,
})
}).trigger('resize')
$(canvas).on('wheel', function (ev) {
ev.preventDefault() // for stackoverflow
var step
if (ev.originalEvent.wheelDelta) {
step = (ev.originalEvent.wheelDelta > 0) ? 0.05 : -0.05
}
if (ev.originalEvent.deltaY) {
step = (ev.originalEvent.deltaY > 0) ? 0.05 : -0.05
}
if (!step) return false // yea..
var new_ratio = ratio + step
var min_ratio = Math.max(vpw / orig_width, vph / orig_height)
var max_ratio = 3.0
if (new_ratio < min_ratio) {
new_ratio = min_ratio
}
if (new_ratio > max_ratio) {
new_ratio = max_ratio
}
// zoom center point
var targetX = ev.originalEvent.clientX || (vpw / 2)
var targetY = ev.originalEvent.clientY || (vph / 2)
// percentages from side
var pX = ((vpx * -1) + targetX) * 100 / width
var pY = ((vpy * -1) + targetY) * 100 / height
// update ratio and dimentsions
ratio = new_ratio
width = orig_width * new_ratio
height = orig_height * new_ratio
// translate view back to center point
var x = ((width * pX / 100) - targetX)
var y = ((height * pY / 100) - targetY)
// don't let viewport go over edges
if (x < 0) {
x = 0
}
if (x + vpw > width) {
x = width - vpw
}
if (y < 0) {
y = 0
}
if (y + vph > height) {
y = height - vph
}
vpx = x * -1
vpy = y * -1
})
var is_down, is_drag, last_drag
$(canvas).on('mousedown', function (ev) {
is_down = true
is_drag = false
last_drag = { x: ev.clientX, y: ev.clientY }
})
$(canvas).on('mousemove', function (ev) {
is_drag = true
if (is_down) {
var x = vpx - (last_drag.x - ev.clientX)
var y = vpy - (last_drag.y - ev.clientY)
if (x <= 0 && vpw < x + width) {
vpx = x
}
if (y <= 0 && vph < y + height) {
vpy = y
}
last_drag = { x: ev.clientX, y: ev.clientY }
}
})
$(canvas).on('mouseup', function (ev) {
is_down = false
last_drag = null
var was_click = !is_drag
is_drag = false
if (was_click) {
}
})
$(canvas).css({ position: 'absolute', top: 0, left: 0 }).appendTo(document.body)
function animate () {
window.requestAnimationFrame(animate)
canvasContext.clearRect(0, 0, canvas.width, canvas.height)
canvasContext.lineWidth = 1
canvasContext.strokeStyle = '#ccc'
var step = 100 * ratio
for (var x = vpx; x < width + vpx; x += step) {
canvasContext.beginPath()
canvasContext.moveTo(x, vpy)
canvasContext.lineTo(x, vpy + height)
canvasContext.stroke()
}
for (var y = vpy; y < height + vpy; y += step) {
canvasContext.beginPath()
canvasContext.moveTo(vpx, y)
canvasContext.lineTo(vpx + width, y)
canvasContext.stroke()
}
canvasContext.strokeRect(vpx, vpy, width, height)
canvasContext.beginPath()
canvasContext.moveTo(vpx, vpy)
canvasContext.lineTo(vpx + width, vpy + height)
canvasContext.stroke()
canvasContext.beginPath()
canvasContext.moveTo(vpx + width, vpy)
canvasContext.lineTo(vpx, vpy + height)
canvasContext.stroke()
canvasContext.restore()
}
animate()
})
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</head>
<body>
<canvas class="main"></canvas>
</body>
</html>
I took #Phrogz's answer as a basis and made a small library that enables canvas with dragging, zooming and rotating.
Here is the example.
var canvas = document.getElementById('canvas')
//assuming that #param draw is a function where you do your main drawing.
var control = new CanvasManipulation(canvas, draw)
control.init()
control.layout()
//now you can drag, zoom and rotate in canvas
You can find more detailed examples and documentation on the project's page
Faster
Using ctx.setTransform gives you more performance than multiple matrix calls ctx.translate, ctx.scale, ctx.translate.
No need for complex transformation inversions as and expensive DOM matrix calls tp converts point between zoomed and screen coordinate systems.
Flexible
Flexibility as you don't need to use ctx.save and ctx.restore if you are rendering content at using different transforms. Returning to the transform with ctx.setTransform rather than the potentially frame rate wreaking ctx.restorecall
Easy to invert the transform and get the world coordinates of a (screen) pixel position and the other way round.
Examples
Using mouse and mouse wheel to zoom in and out at mouse position
An example using this method to scale page content at a point (mouse) via CSS transform CSS Demo at bottom of answer also has a copy of the demo from the next example.
And an example of this method used to scale canvas content at a point using setTransform
How
Given a scale and pixel position you can get the new scale as follow...
const origin = {x:0, y:0}; // canvas origin
var scale = 1; // current scale
function scaleAt(x, y, scaleBy) { // at pixel coords x, y scale by scaleBy
scale *= scaleBy;
origin.x = x - (x - origin.x) * scaleBy;
origin.y = y - (y - origin.y) * scaleBy;
}
To position the canvas and draw content
ctx.setTransform(scale, 0, 0, scale, origin.x, origin.y);
ctx.drawImage(img, 0, 0);
To use if you have the mouse coordinates
const zoomBy = 1.1; // zoom in amount
scaleAt(mouse.x, mouse.y, zoomBy); // will zoom in at mouse x, y
scaleAt(mouse.x, mouse.y, 1 / zoomBy); // will zoom out by same amount at mouse x,y
To restore the default transform
ctx.setTransform(1,0,0,1,0,0);
The inversions
To get the coordinates of a point in the zoomed coordinate system and the screen position of a point in the zoomed coordinate system
Screen to world
function toWorld(x, y) { // convert to world coordinates
x = (x - origin.x) / scale;
y = (y - origin.y) / scale;
return {x, y};
}
World to screen
function toScreen(x, y) {
x = x * scale + origin.x;
y = y * scale + origin.y;
return {x, y};
}