Measure distance between two HTML elements' centers - javascript

If I have HTML elements as follows:
<div id="x"></div>
<div id="y" style="margin-left:100px;"></div>
...how do I find the distance between them in pixels using JavaScript?

Get their positions, and use the Pythagorean Theorem to determine the distance between them...
function getPositionAtCenter(element) {
const {top, left, width, height} = element.getBoundingClientRect();
return {
x: left + width / 2,
y: top + height / 2
};
}
function getDistanceBetweenElements(a, b) {
const aPosition = getPositionAtCenter(a);
const bPosition = getPositionAtCenter(b);
return Math.hypot(aPosition.x - bPosition.x, aPosition.y - bPosition.y);
}
const distance = getDistanceBetweenElements(
document.getElementById("x"),
document.getElementById("y")
);
If you browser doesn't support Math.hypot(), you can use instead:
Math.sqrt(
Math.pow(aPosition.x - bPosition.x, 2) +
Math.pow(aPosition.y - bPosition.y, 2)
);
The Pythagorean Theorem relates to the relationship between the sides of a right-angled triangle.
The elements are plotted on a Cartesian coordinate system (with origin in top left), so you can imagine a right-angled triangle between the elements' coordinates (the unknown side is the hypotenuse).
You can modify the equation to get the value of c by getting the square root of the other side.
Then, you simply plug the values in (the x and y are the differences between the elements once their centers are determined) and you will find the length of the hypotenuse, which is the distance between the elements.

as far as div's are now empty, the basic idea is to measure the distance between their left top corners
distX = y.offsetLeft - x.offsetLeft;
distY = y.offsetTop - x.offsetTop;
distance = Math.sqrt(distX*distX + distY*distY);
alert(Math.floor(distance));
but you have to substract first div's height and width, if you put something inside. This method has some issues with support and border width of elements in different browsers.
anyway take a look at Fiddle
Note, that even with content (if you don't change it with css) divs will be 100% width, so if you want just to measure br's height use:
distance = = y.offsetTop - x.offsetTop;

Related

Defining boundaries of a hitbox in Javascript

I have been working on a simple brick breaker game in Javascript for the past few weeks and come across an issue with creating a hitbox that I have no clue on where to begin fixing it.
A brief overview of what's going on:
My Hitbox class takes in a gameObject (a ball, or a brick, etc; defined elsewhere) and in the constructor sets its own position, width and height as the given game object's respectively. It has 2 arrays that I use for collision detection, one for the left/right sides and one for the top/bottom. Lastly it has 4 objects (or in this case positions) that define the "side" of the hitbox. These objects contain a singular value and an array of values.
Since every hitbox is going to be some form of a rectangle, I can simplify the creation of a hitbox because both left/right sides are vertical lines (all x values will be the same) and both top/bottom are horizontal lines. (all y values will be the same)
For instance, starting with the left side of the hitbox:
I know the starting point will be the top left corner of the object (gameObject.position.y) and the ending point will be the bottom left corner. (gameObject.position.y + gameObject.height) So, all I need to do is loop through all the y values up to the ending point to create the left side of the hitbox. So, the positions of the left side of the hitbox would be:
(gameObject.position.x, sides[0])
(gameObject.position.x, sides[1])
(gameObject.position.x, sides[2])
...
(gameObject.position.x, sides[n])
And the same logic for the right side:
(gameObject.position.x + gameObject.width, sides[0])
(gameObject.position.x + gameObject.width, sides[1])
(gameObject.position.x + gameObject.width, sides[2])
...
(gameObject.position.x + gameObject.width, sides[n])
Here's the class itself. You can ignore the console.log() functions in the buildHitbox() member function; I was just using those for testing.
export default class Hitbox{
constructor(gameObject){
this.position = gameObject.position;
this.width = gameObject.width;
this.height = gameObject.height;
this.sides = [];
this.tops = [];
this.leftSide = {
x: gameObject.position.x,
y: this.sides
};
this.rightSide = {
x: gameObject.position.x + gameObject.width,
y: this.sides
};
this.topSide = {
x: this.tops,
y: gameObject.position.y
};
this.bottomSide = {
x: this.tops,
y: gameObject.position.y + gameObject.height
};
}
buildHitbox(){
// left/right sides of hitbox
for(var sideIndex = this.position.y; sideIndex < this.height; sideIndex++){
this.sides.push(sideIndex);
}
//console.log("hitbox left side: ", this.leftSide); //SUCCESSFULL
//console.log("hitbox right side: ", this.rightSide); //SUCCESSFULL
// top/bottom sides of hitbox
for(var topIndex = this.position.x; topIndex < this.width; topIndex++){
this.tops.push(topIndex);
}
//works for far left brick but not far right
//console.log("hitbox tops sides: ", this.topSide);
//console.log("hitbox bottom sides: ", this.bottomSide);
//works for far left brick but not for right
}
Here's my issue
The test case I'm using for this is a brick at the top left corner of the canvas and at the top right corner of the canvas. The canvas itself is 1000px x 600px, so the left brick will start at position (0,0) and the right brick will start at (950,0) because each bricks' width is 50px.
The left and right sides of the hitbox are working correctly, (seen below)
values of left/right side arrays
but when I apply the same logic to the top and bottom array, it works as intended for the left brick but doesn't populate the array for the right brick.
top/bottom values showing empty arrays
I have already checked to make sure that the right brick shows it's starting x position as 950, and it does so I know it's not an issue with my level generator.
Maybe it has something to do with the bounds of the canvas?
Maybe it has something to do with the IDE I'm using? (codesandbox.io)
Maybe it has something to do with how things are rendered in javascript?
Any help with this would be greatly appreciated and I am willing to share more aspects of the game if needed. I am just completely stuck at the moment and can't really progress until I figure this one out.
It is not anything else but the code bug.
You should use topIndex < this.position.x + this.width instead of topIndex < this.width.
The right code block is here:
for(var sideIndex = this.position.y; sideIndex < this.position.y + this.height; sideIndex++){
this.sides.push(sideIndex);
}
console.log("hitbox left side: ", this.leftSide); //SUCCESSFULL
console.log("hitbox right side: ", this.rightSide); //SUCCESSFULL
// top/bottom sides of hitbox
for(var topIndex = this.position.x; topIndex < this.position.x + this.width; topIndex++){
this.tops.push(topIndex);
}
//works for far left brick but not far right
console.log("hitbox tops sides: ", this.topSide);
console.log("hitbox bottom sides: ", this.bottomSide);

How to plot x,y coordinates of item on a image in html

I need to place/plot a pin(Small image to point the parts of person) on a image(For example: A image of person).
I am getting x,y,height,width values from server for a specific pin and i am creating one div element for each pin and assigning x,y,height,width values.
In Javascript, i am calculating view scale value in below mentioned way and multiply view scale with x,y,width,height and assign it into pin div element.
const screenwidth = screen.width;
const screenheight = screen.height;
viewscale = Math.min(screenwidth / mainImagewidth, screenheight / mainImageheight);
I am not able to place the pin on exact position of main image. Please help me if someone has idea of this logic.
Update:
Please find below the explanation through image.
Red Color rectangle is the screen. Green is the main image, let's say human image. Black color rectangle is a pin to describe a part in human image. I am getting x,y coordinates for this black colored rect pin from server.
Assuming I've understood this correctly, here's a possible demo solution:
First, define a config for your pin:
const pinConfig = {
width: 45,
height: 45,
offsetLeft: 40,
offsetTop: 75
};
Define a simple key/value map for getting the correct size type (width or height) when given an offset type (left or top):
const offsetTypeToSizeDimensionMap = {
left: 'width',
top: 'height'
};
Use a simple fn that calculates offset position relative to size. size / 2 because we need to compensate for the size of the pin, so positioning is based on the center of the element.
const calcRelativeOffsetPos = (offsetPos, size) => offsetPos - (size / 2);
Here's a style attribute string generating fn, accepts an object (our pinConfig above, basically):
const generateStylesString = (stylesConfig) => {
return Object.keys(stylesConfig).map((styleProp) => {
if (styleProp.includes('offset')){
const stylePropName = styleProp.split('offset')[1].toLowerCase();
const relativeSizeTypeByOffsetType = offsetTypeToSizeDimensionMap[stylePropName];
const calculatedRelativeOffsetPos = calcRelativeOffsetPos(stylesConfig[styleProp], stylesConfig[relativeSizeTypeByOffsetType]);
return stylePropName + ': ' + calculatedRelativeOffsetPos + 'px; ';
}
return styleProp + ': ' + stylesConfig[styleProp] + 'px; ';
}).join('');
};
Finally, set style attr to .child-parent node:
document.querySelector('.child-image').setAttribute('style', generateStylesString(pinConfig));
Here's an example on Codepen: https://codepen.io/Inlesco/pen/xLwjLy?editors=1010
If you need the React way, it's easy - just concat the generated inline styles string to a JSX element when mapping out the elements and that's it.
Feel free to provide feedback, so we can improve this :)

Zoom my drawing on the background [duplicate]

This question already has an answer here:
HTML5 canvas zoom where mouse coordinates
(1 answer)
Closed 8 years ago.
I make program like a paint with HTML5 canvas and javascript. Drawing takes place on the background image. How to zoom my drawing on the background together.
Before zoom it:
After zoom it (need this result):
Note: zoom should be where clicked with the mouse on the background image
I've done this before!
First of all, I set a zoom level attribute on my canvas.
Main.canvas.zoomX = 1;
Main.canvas.zoomY = 1;
I also retain the original size of the canvas for reference.
Main.canvas.originW = Main.canvas.width;
Main.canvas.originH = Main.canvas.height;
I also retain the original left and top of the canvas for reference.
Main.canvas.gLeftStart = 0;
Main.canvas.gTopStart = 0;
I then set a zoom percentage. The zoom level will be adjusted by this amount every time that the zoom event occurs.
Main.canvas.zoomPerc = 0.05;
Next, I set an event listener on my canvas to watch for mousewheel.
Main.canvas.addEventListener('wheel', zoom, true);
Now, I'm going to write a quick function to retrieve the zoom, then I'll explain it.
function zoom(evt)
{
var x;
var y;
Main.canvas.xLayerS = (evt.layerX + (Main.canvas.gLeftStart * -1)) / (Main.canvas.originW * Main.canvas.zoomX);
Main.canvas.yLayerS = (evt.layerY + (Main.canvas.gTopStart * -1)) / (Main.canvas.originH * Main.canvas.zoomY);
Main.canvas.leftPerc = Main.canvas.gLeftStart / (Main.canvas.originW * Main.canvas.zoomX);
Main.canvas.topPerc = Main.canvas.gTopStart / (Main.canvas.originH * Main.canvas.zoomY);
if(evt.deltaY > 1)
{
Main.canvas.zoomX *= 1 + Main.canvas.zoomPerc;
Main.canvas.zoomY *= 1 + Main.canvas.zoomPerc;
}
else
{
Main.canvas.zoomX *= 1 - Main.canvas.zoomPerc;
Main.canvas.zoomY *= 1 - Main.canvas.zoomPerc;
}
var iiDS;
var cmd;
Main.canvas.xLayer = Main.canvas.xLayerS * (Main.canvas.originW * Main.canvas.zoomX);
Main.canvas.yLayer = Main.canvas.yLayerS * (Main.canvas.originH * Main.canvas.zoomY);
Main.context.clearRect(0, 0, Main.canvas.width, Main.canvas.height);
Main.context.beginPath();
Main.canvas.gLeftStart = (evt.layerX - Main.canvas.xLayer);
Main.canvas.gTopStart = (evt.layerY - Main.canvas.yLayer);
for(iiDS = 0; iiDS < Main.dataPoints.length; iiDS++)
{
if(iiDS === 0)
{
cmd = 'moveTo';
}
else
{
cmd = 'lineTo';
}
Main.dataPoints[iiDS].xPerc = Main.dataPoints[iiDS].x / Main.range.x;
Main.dataPoints[iiDS].yPerc = Main.dataPoints[iiDS].y / Main.range.y;
x = Main.canvas.gLeftStart + (Main.dataPoints[iiDS].xPerc * (Main.canvas.originW * Main.canvas.zoomX));
y = Main.canvas.gTopStart + (Main.dataPoints[iiDS].yPerc * (Main.canvas.originH * Main.canvas.zoomY));
Main.context[cmd](x, y);
}
Main.context.stroke();
}
Now that your canvas has been re-sized, you will need to redraw whatever was in it. Remember, any time that you re-size a canvas, you clear the canvas. If your canvas was holding an image, then that's simple, redraw that image at that size. If you canvas was holding data points (like a chart) then I would suggest that you make your data points have percentage like (probably a word for that) positions along your chart, not pixel positions.
More importantly though, I do not suggest that you ever re-size and re-position your canvas on zoom. Your page can get jumbled up and sloppy that way. Instead, use the percentages for size (like I showed you) and use the values for left and top positioning as starting points in your drawing. If a data point was a certain percentage of a way across a chart, it can be drawn at any size. Plus, you can draw outside of your canvas, it just won't be visible. Your canvas would then be more like a view-port.
You can do some really impressive charting this way, which a lot of companies pay a lot of money for. Have fun!
Did you try Context2d.scale(x, y)? You could do the following
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
context.scale(2, 2);
paintBackGround(context);
paintForeGround(context);
scale(factorWidth, factorHeight) Scales all coordinates in the canvas by the factors, so it will scale the background and the drawing. The example would double the size. You don't have to scale your coordinates by yourself, just let canvas do that for you.
Here is an example :
http://www.html5canvastutorials.com/advanced/html5-canvas-transform-scale-tutorial/
The only problem here: you need to scale before you draw, so you need a model that contains the original drawing in original unscaled coordinates, that can be drawn after scaling (paintForeGround() in my example)
Scale() is part of so called Transformations. You can Translate (move along a vector) rotate and scale the content of a canvas by using buildin functions of canvas. Just take a look at the html5canvastutorials. This works with matrix-mutliplications in the background, but it is really simple to use.

mouse position to isometric tile including height

Struggeling translating the position of the mouse to the location of the tiles in my grid. When it's all flat, the math looks like this:
this.position.x = Math.floor(((pos.y - 240) / 24) + ((pos.x - 320) / 48));
this.position.y = Math.floor(((pos.y - 240) / 24) - ((pos.x - 320) / 48));
where pos.x and pos.y are the position of the mouse, 240 and 320 are the offset, 24 and 48 the size of the tile. Position then contains the grid coordinate of the tile I'm hovering over. This works reasonably well on a flat surface.
Now I'm adding height, which the math does not take into account.
This grid is a 2D grid containing noise, that's being translated to height and tile type. Height is really just an adjustment to the 'Y' position of the tile, so it's possible for two tiles to be drawn in the same spot.
I don't know how to determine which tile I'm hovering over.
edit:
Made some headway... Before, I was depending on the mouseover event to calculate grid position. I just changed this to do the calculation in the draw loop itself, and check if the coordinates are within the limits of the tile currently being drawn. creates some overhead tho, not sure if I'm super happy with it but I'll confirm if it works.
edit 2018:
I have no answer, but since this ha[sd] an open bounty, help yourself to some code and a demo
The grid itself is, simplified;
let grid = [[10,15],[12,23]];
which leads to a drawing like:
for (var i = 0; i < grid.length; i++) {
for (var j = 0; j < grid[0].length; j++) {
let x = (j - i) * resourceWidth;
let y = ((i + j) * resourceHeight) + (grid[i][j] * -resourceHeight);
// the "+" bit is the adjustment for height according to perlin noise values
}
}
edit post-bounty:
See GIF. The accepted answer works. The delay is my fault, the screen doesn't update on mousemove (yet) and the frame rate is low-ish. It's clearly bringing back the right tile.
Source
Intresting task.
Lets try to simplify it - lets resolve this concrete case
Solution
Working version is here: https://github.com/amuzalevskiy/perlin-landscape (changes https://github.com/jorgt/perlin-landscape/pull/1 )
Explanation
First what came into mind is:
Just two steps:
find an vertical column, which matches some set of tiles
iterate tiles in set from bottom to top, checking if cursor is placed lower than top line
Step 1
We need two functions here:
Detects column:
function getColumn(mouseX, firstTileXShiftAtScreen, columnWidth) {
return (mouseX - firstTileXShiftAtScreen) / columnWidth;
}
Function which extracts an array of tiles which correspond to this column.
Rotate image 45 deg in mind. The red numbers are columnNo. 3 column is highlighted. X axis is horizontal
function tileExists(x, y, width, height) {
return x >= 0 & y >= 0 & x < width & y < height;
}
function getTilesInColumn(columnNo, width, height) {
let startTileX = 0, startTileY = 0;
let xShift = true;
for (let i = 0; i < columnNo; i++) {
if (tileExists(startTileX + 1, startTileY, width, height)) {
startTileX++;
} else {
if (xShift) {
xShift = false;
} else {
startTileY++;
}
}
}
let tilesInColumn = [];
while(tileExists(startTileX, startTileY, width, height)) {
tilesInColumn.push({x: startTileX, y: startTileY, isLeft: xShift});
if (xShift) {
startTileX--;
} else {
startTileY++;
}
xShift = !xShift;
}
return tilesInColumn;
}
Step 2
A list of tiles to check is ready. Now for each tile we need to find a top line. Also we have two types of tiles: left and right. We already stored this info during building matching tiles set.
function getTileYIncrementByTileZ(tileZ) {
// implement here
return 0;
}
function findExactTile(mouseX, mouseY, tilesInColumn, tiles2d,
firstTileXShiftAtScreen, firstTileYShiftAtScreenAt0Height,
tileWidth, tileHeight) {
// we built a set of tiles where bottom ones come first
// iterate tiles from bottom to top
for(var i = 0; i < tilesInColumn; i++) {
let tileInfo = tilesInColumn[i];
let lineAB = findABForTopLineOfTile(tileInfo.x, tileInfo.y, tiles2d[tileInfo.x][tileInfo.y],
tileInfo.isLeft, tileWidth, tileHeight);
if ((mouseY - firstTileYShiftAtScreenAt0Height) >
(mouseX - firstTileXShiftAtScreen)*lineAB.a + lineAB.b) {
// WOHOO !!!
return tileInfo;
}
}
}
function findABForTopLineOfTile(tileX, tileY, tileZ, isLeftTopLine, tileWidth, tileHeight) {
// find a top line ~~~ a,b
// y = a * x + b;
let a = tileWidth / tileHeight;
if (isLeftTopLine) {
a = -a;
}
let b = isLeftTopLine ?
tileY * 2 * tileHeight :
- (tileX + 1) * 2 * tileHeight;
b -= getTileYIncrementByTileZ(tileZ);
return {a: a, b: b};
}
Please don't judge me as I am not posting any code. I am just suggesting an algorithm that can solve it without high memory usage.
The Algorithm:
Actually to determine which tile is on mouse hover we don't need to check all the tiles. At first we think the surface is 2D and find which tile the mouse pointer goes over with the formula OP posted. This is the farthest probable tile mouse cursor can point at this cursor position.
This tile can receive mouse pointer if it's at 0 height, by checking it's current height we can verify if this is really at the height to receive pointer, we mark it and move forward.
Then we find the next probable tile which is closer to the screen by incrementing or decrementing x,y grid values depending on the cursor position.
Then we keep on moving forward in a zigzag fashion until we reach a tile which cannot receive pointer even if it is at it's maximum height.
When we reach this point the last tile found that were at a height to receive pointer is the tile that we are looking for.
In this case we only checked 8 tiles to determine which tile is currently receiving pointer. This is very memory efficient in comparison to checking all the tiles present in the grid and yields faster result.
One way to solve this would be to follow the ray that goes from the clicked pixel on the screen into the map. For that, just determine the camera position in relation to the map and the direction it is looking at:
const camPos = {x: -5, y: -5, z: -5}
const camDirection = { x: 1, y:1, z:1}
The next step is to get the touch Position in the 3D world. In this certain perspective that is quite simple:
const touchPos = {
x: camPos.x + touch.x / Math.sqrt(2),
y: camPos.y - touch.x / Math.sqrt(2),
z: camPos.z - touch.y / Math.sqrt(2)
};
Now you just need to follow the ray into the layer (scale the directions so that they are smaller than one of your tiles dimensions):
for(let delta = 0; delta < 100; delta++){
const x = touchPos.x + camDirection.x * delta;
const y = touchPos.y + camDirection.y * delta;
const z = touchPos.z + camDirection.z * delta;
Now just take the tile at xz and check if y is smaller than its height;
const absX = ~~( x / 24 );
const absZ = ~~( z / 24 );
if(tiles[absX][absZ].height >= y){
// hanfle the over event
}
I had same situation on a game. first I tried with mathematics, but when I found that the clients wants to change the map type every day, I changed the solution with some graphical solution and pass it to the designer of the team. I captured the mouse position by listening the SVG elements click.
the main graphic directly used to capture and translate the mouse position to my required pixel.
https://blog.lavrton.com/hit-region-detection-for-html5-canvas-and-how-to-listen-to-click-events-on-canvas-shapes-815034d7e9f8
https://code.sololearn.com/Wq2bwzSxSnjl/#html
Here is the grid input I would define for the sake of this discussion. The output should be some tile (coordinate_1, coordinate_2) based on visibility on the users screen of the mouse:
I can offer two solutions from different perspectives, but you will need to convert this back into your problem domain. The first methodology is based on coloring tiles and can be more useful if the map is changing dynamically. The second solution is based on drawing coordinate bounding boxes based on the fact that tiles closer to the viewer like (0, 0) can never be occluded by tiles behind it (1,1).
Approach 1: Transparently Colored Tiles
The first approach is based on drawing and elaborated on here. I must give the credit to #haldagan for a particularly beautiful solution. In summary it relies on drawing a perfectly opaque layer on top of the original canvas and coloring every tile with a different color. This top layer should be subject to the same height transformations as the underlying layer. When the mouse hovers over a particular layer you can detect the color through canvas and thus the tile itself. This is the solution I would probably go with and this seems to be a not so rare issue in computer visualization and graphics (finding positions in a 3d isometric world).
Approach 2: Finding the Bounding Tile
This is based on the conjecture that the "front" row can never be occluded by "back" rows behind it. Furthermore, "closer to the screen" tiles cannot be occluded by tiles "farther from the screen". To make precise the meaning of "front", "back", "closer to the screen" and "farther from the screen", take a look at the following:
.
Based on this principle the approach is to build a set of polygons for each tile. So firstly we determine the coordinates on the canvas of just box (0, 0) after height scaling. Note that the height scale operation is simply a trapezoid stretched vertically based on height.
Then we determine the coordinates on the canvas of boxes (1, 0), (0, 1), (1, 1) after height scaling (we would need to subtract anything from those polygons which overlap with the polygon (0, 0)).
Proceed to build each boxes bounding coordinates by subtracting any occlusions from polygons closer to the screen, to eventually get coordinates of polygons for all boxes.
With these coordinates and some care you can ultimately determine which tile is pointed to by a binary search style through overlapping polygons by searching through bottom rows up.
It also matters what else is on the screen. Maths attempts work if your tiles are pretty much uniform. However if you are displaying various objects and want the user to pick them, it is far easier to have a canvas-sized map of identifiers.
function poly(ctx){var a=arguments;ctx.beginPath();ctx.moveTo(a[1],a[2]);
for(var i=3;i<a.length;i+=2)ctx.lineTo(a[i],a[i+1]);ctx.closePath();ctx.fill();ctx.stroke();}
function circle(ctx,x,y,r){ctx.beginPath();ctx.arc(x,y,r,0,2*Math.PI);ctx.fill();ctx.stroke();}
function Tile(h,c,f){
var cnv=document.createElement("canvas");cnv.width=100;cnv.height=h;
var ctx=cnv.getContext("2d");ctx.lineWidth=3;ctx.lineStyle="black";
ctx.fillStyle=c;poly(ctx,2,h-50,50,h-75,98,h-50,50,h-25);
poly(ctx,50,h-25,2,h-50,2,h-25,50,h-2);
poly(ctx,50,h-25,98,h-50,98,h-25,50,h-2);
f(ctx);return ctx.getImageData(0,0,100,h);
}
function put(x,y,tile,image,id,map){
var iw=image.width,tw=tile.width,th=tile.height,bdat=image.data,fdat=tile.data;
for(var i=0;i<tw;i++)
for(var j=0;j<th;j++){
var ijtw4=(i+j*tw)*4,a=fdat[ijtw4+3];
if(a!==0){
var xiyjiw=x+i+(y+j)*iw;
for(var k=0;k<3;k++)bdat[xiyjiw*4+k]=(bdat[xiyjiw*4+k]*(255-a)+fdat[ijtw4+k]*a)/255;
bdat[xiyjiw*4+3]=255;
map[xiyjiw]=id;
}
}
}
var cleanimage;
var pickmap;
function startup(){
var water=Tile(77,"blue",function(){});
var field=Tile(77,"lime",function(){});
var tree=Tile(200,"lime",function(ctx){
ctx.fillStyle="brown";poly(ctx,50,50,70,150,30,150);
ctx.fillStyle="forestgreen";circle(ctx,60,40,30);circle(ctx,68,70,30);circle(ctx,32,60,30);
});
var sheep=Tile(200,"lime",function(ctx){
ctx.fillStyle="white";poly(ctx,25,155,25,100);poly(ctx,75,155,75,100);
circle(ctx,50,100,45);circle(ctx,50,80,30);
poly(ctx,40,70,35,80);poly(ctx,60,70,65,80);
});
var cnv=document.getElementById("scape");
cnv.width=500;cnv.height=400;
var ctx=cnv.getContext("2d");
cleanimage=ctx.getImageData(0,0,500,400);
pickmap=new Uint8Array(500*400);
var tiles=[water,field,tree,sheep];
var map=[[[0,0],[1,1],[1,1],[1,1],[1,1]],
[[0,0],[1,1],[1,2],[3,2],[1,1]],
[[0,0],[1,1],[2,2],[3,2],[1,1]],
[[0,0],[1,1],[1,1],[1,1],[1,1]],
[[0,0],[0,0],[0,0],[0,0],[0,0]]];
for(var x=0;x<5;x++)
for(var y=0;y<5;y++){
var desc=map[y][x],tile=tiles[desc[0]];
put(200+x*50-y*50,200+x*25+y*25-tile.height-desc[1]*20,
tile,cleanimage,x+1+(y+1)*10,pickmap);
}
ctx.putImageData(cleanimage,0,0);
}
var mx,my,pick;
function mmove(event){
mx=Math.round(event.offsetX);
my=Math.round(event.offsetY);
if(mx>=0 && my>=0 && mx<cleanimage.width && my<cleanimage.height && pick!==pickmap[mx+my*cleanimage.width])
requestAnimationFrame(redraw);
}
function redraw(){
pick=pickmap[mx+my*cleanimage.width];
document.getElementById("pick").innerHTML=pick;
var ctx=document.getElementById("scape").getContext("2d");
ctx.putImageData(cleanimage,0,0);
if(pick!==0){
var temp=ctx.getImageData(0,0,cleanimage.width,cleanimage.height);
for(var i=0;i<pickmap.length;i++)
if(pickmap[i]===pick)
temp.data[i*4]=255;
ctx.putImageData(temp,0,0);
}
}
startup(); // in place of body.onload
<div id="pick">Move around</div>
<canvas id="scape" onmousemove="mmove(event)"></canvas>
Here the "id" is a simple x+1+(y+1)*10 (so it is nice when displayed) and fits into a byte (Uint8Array), which could go up to 15x15 display grid already, and there are wider types available too.
(Tried to draw it small, and it looked ok on the snippet editor screen but apparently it is still too large here)
Computer graphics is fun, right?
This is a special case of the more standard computational geometry "point location problem". You could also express it as a nearest neighbour search.
To make this look like a point location problem you just need to express your tiles as non-overlapping polygons in a 2D plane. If you want to keep your shapes in a 3D space (e.g. with a z buffer) this becomes the related "ray casting problem".
One source of good geometry algorithms is W. Randolf Franklin's website and turf.js contains an implementation of his PNPOLY algorithm.
For this special case we can be even faster than the general algorithms by treating our prior knowledge about the shape of the tiles as a coarse R-tree (a type of spatial index).

Get curved result set rather than angular in JavaScript (maths help needed)

I've got a script that creates a gradient by shading cells based on their distance from a set of coordinates. What I want to do is make the gradient circular rather than the diamond shape that it currently is. You can see an en example here: http://jsbin.com/uwivev/9/edit
var row = 5, col = 5, total_rows = 20, total_cols = 20;
$('table td').each(function(index, item) {
// Current row and column
var current_row = $(item).parent().index(),
current_col = $(item).index();
// Percentage based on location, always using positive numbers
var percentage_row = Math.abs(current_row-row)/total_rows;
var percentage_col = Math.abs(current_col-col)/total_cols;
// I'm thinking this is what I need to change to achieve the curve I'm after
var percentage = (percentage_col+percentage_row)/2;
$(this).find('div').fadeTo(0,percentage*3);
});
If you can give me hand with the right maths function to get the curve I'm after that would be great! Thanks!
Darren
// Current row and column
var current_row = $(item).parent().index(),
current_col = $(item).index();
// distance away from the bright pixel
var dist = Math.sqrt(Math.pow(current_row - row, 2) + Math.pow(current_col - col, 2))
// do something with dist, you might change this
var percentage = dist / total_cols;
$(this).find('div').fadeTo(0,percentage*3);
You can use the square of the distance formula:
((current_row - row)*(current_row - row) + (current_col - col)*(current_col - col))
then multiply it by whatever scale factor you need.
Here is a circle drawing procudure I wrote many moons ago in Pascal which you can use as pseudo code to understand how to color pixels at the radius from an (X,Y) and work your way in. Multiple shrinking circles should cover the entire area you need. The code also gives you the formula for accessing the radius.
PROCEDURE DrawCircle(X,Y,Radius:Integer);
VAR A,B,Z:LongInt;
BEGIN
Z:=Round(Sqrt(Sqr(LongInt(Radius))/2));
FOR A:=Z TO Radius DO
FOR B:=0 TO Z DO
IF Radius=Round(Sqrt(A*A+B*B)) THEN
BEGIN
PutPixel(X+A,Y+B,8);
PutPixel(X+A,Y-B,9);
PutPixel(X-A,Y+B,10);
PutPixel(X-A,Y-B,11);
PutPixel(X+B,Y+A,12);
PutPixel(X+B,Y-A,13);
PutPixel(X-B,Y+A,14);
PutPixel(X-B,Y-A,15);
END;
END;
NB: "Longint()" is a compiler typecast for larger numeric computations so don't let that worry you.
NB: Inner-most brackets are executed first.

Categories

Resources