I'm making a game in html5 canvas. I'm using jquery so I can get the click event and the clicks x,y coordinates. I have an array of unit objects and a tiled terrain (also an array). The unit objects have bounding box information, their position, and their type.
What is the most effecient way to map this click event to one of the units?
Loop through the unit objects and determine which was clicked like so:
// 'e' is the DOM event object
// 'c' is the canvas element
// 'units' is the array of unit objects
// (assuming each unit has x/y/width/height props)
var y = e.pageY,
x = e.pageX,
cOffset = $(c).offset(),
clickedUnit;
// Adjust x/y values so we get click position relative to canvas element
x = x - cOffset.top;
y = y - cOffset.left;
// Note, if the canvas element has borders/outlines/margins then you
// will need to take these into account too.
for (var i = -1, l = units.length, unit; ++i < l;) {
unit = units[i];
if (
y > unit.y && y < unit.y + unit.height &&
x > unit.x && x < unit.x + unit.width
) {
// Escape upon finding first matching unit
clickedUnit = unit;
break;
}
}
// Do something with `clickedUnit`
Note, this won't handle complex intersecting objects or z-index issues etc... just a starting point really.
Related
I am working on a project of mine that has a plane with a grid that allows you to just snap on objects. Where I am stuck is, I can't seem to figure out how to tell if an object overhangs the plane, than don't add it if the user triggers a click.
Currently:
event.preventDefault();
this.mouse.x = ( event.clientX / this.renderer.domElement.width ) * 2 - 1;
this.mouse.y = - ( event.clientY / this.renderer.domElement.height ) * 2 + 1;
this.raycaster.setFromCamera( this.mouse, this.camera.cam );
var intersects = this.raycaster.intersectObjects( this.blocks );
if ( intersects.length > 0 ) {
var intersect = intersects[ 0 ];
if ( this.isShiftDown ) { // see if shift is down to remove
if ( intersect.object != this.plane ) {
this.scene.remove( intersect.object );
this.blocks.splice( this.blocks.indexOf( intersect.object ), 1 );
}
} else if(intersect.object == this.plane ){ // add room to canvas
var voxel = new THREE.Mesh( this.room.cubeGeometry.clone(), this.room.cubeMaterial.clone() );
voxel.position.copy( intersect.point ).add( intersect.face.normal );
voxel.position.divideScalar( 50 ).floor().multiplyScalar( 50 ).addScalar( 25 );
this.scene.add( voxel );
this.blocks.push( voxel );
var domEvents = new THREEx.DomEvents(this.camera.cam, this.renderer.domElement)
console.log(voxel);
// DOM events for inside 3d rendering
domEvents.addEventListener(voxel, 'mouseover', this.onDocumentMouseOverCube.bind(this),false);
domEvents.addEventListener(voxel, 'mouseout', this.onDocumentMouseOutCube.bind(this),false);
}
this.render();
}
So what happens here is: first we get our mouse position - send it to our raycaster to setup. Than we check to see if we have intersected any objects. Now this is all great except. If an object is bigger than a single 20x20 slot, it will over hang the plane canvas. I want to be able to deny the user from placing anything if the object over hangs our plane. The plane object is always at position this.blocks[0] So as you can see in the else if we check to see if its on plane. But I am not accounting for, excess of object overhang
Any suggestions on the best way to accomplish this?
Compute the bounding box of your object.
var voxel = new THREE.Mesh( this.room.cubeGeometry.clone(), this.room.cubeMaterial.clone() );
voxel.geometry.computeBoundingBox();
When the user clicks on the plane, move the object there and compare the intersection coordinate (x,z) with the bounding box min and max (x,z) values.
If one of the bounding box values is bigger or smaller than the plane, then it overhangs.
This is just an idea, I have not tested this. If you need help with the code, let me know.
As per Falk's request to share my function I created off of his answer.
When dealing with our volex on creation the key here is to set a cube.geometry.computeBoundingBox() - on every volex creation. Reason be, by default we wont have a min or max bounding size. Which to be able to grab our depth and width of our object this is crucial.
We already know that the user is on a PlaneBufferGeometry or plane in my case. So we pass our volex and plane to a custom function called checkOverlapPlane(volex,plane)
Now in a jiffy, this function grabs our plane and volex max and min boundaries. In addition we grab the volex position which is crucial to figure out the volex position on the x and z axis. We than convert all of our variables to positive numbers only for the fact that it makes it easy to deal with positive numbers and add our different x and z. After converting our intagers to positive intagers we than get our total position of the min and max volex z and the volex x. Which in turn is compared with our plane min x z and max x z to return whether the volex is hanging off of our plane.
Here is the code:
checkOverlapPlane: function(voxel,plane) { // THIS IS USED TO TELL US IF WE ARE OUTSIDE THE GRIDS BOUNDRIES
// Now before doing anything lets check out min and max boundries to see if our x and z are either bigger or smaller
var planeMX = plane.geometry.boundingBox.max.x;
var planeMZ = plane.geometry.boundingBox.max.z;
var planemX = plane.geometry.boundingBox.min.x;
var planemZ = plane.geometry.boundingBox.min.z;
//lets get our voxel min and max vars
var voxelMX = voxel.geometry.boundingBox.max.x;
var voxelMZ = voxel.geometry.boundingBox.max.z;
var voxelmX = voxel.geometry.boundingBox.min.x;
var voxelmZ = voxel.geometry.boundingBox.min.z;
// we need to get our voxel position ad to do some math to see if voxel min and max do not go beyound our boundries for our plane
var voxelPX = voxel.position.x;
var voxelPZ = voxel.position.z;
// lets do some calucation and condition
// first lets turn everything over to positive for some easy calcuation
voxelPX = Math.abs(voxelPX); // positions on world view
voxelPZ = Math.abs(voxelPZ);
voxelmZ = Math.abs(voxelmZ); // min and max values of our voxel
voxelmX = Math.abs(voxelmX);
voxelMZ = Math.abs(voxelMZ);
voxelMX = Math.abs(voxelMX);
planemZ = Math.abs(planemZ); // min and max values of our plane our voxels are on
planemX = Math.abs(planemX);
planeMZ = Math.abs(planeMZ);
planeMX = Math.abs(planeMX);
// now lets calucate X and Z to see if total goes over any of our planes min and max X or Y - remember everything is position but so we can add it to see if its above - we will not actually change the direct objects values
var totalPositionVoxelminZ = (voxelPZ + voxelmZ);
var totalPositionVoxelminX = (voxelPX + voxelmX);
var totalPositionVoxelMAXZ = (voxelPZ + voxelMZ);
var totalPositionVoxelMAXX = (voxelPX + voxelMX);
// now lets compare
if(totalPositionVoxelminZ > planemZ)
return false; // do nothing
else if(totalPositionVoxelminX > planemX)
return false; // do nothing
else if(totalPositionVoxelMAXZ > planeMZ)
return false; // do nothing
else if(totalPositionVoxelMAXX > planeMX)
return false; // do nothing
else
return true;
},
Here is the results:
Feel free to use and incorporate this in any project!
I am building an app using HTML5 in which a grid is drawn. I have some shapes on it that you can move.
What I'm trying to do is to snap objects to some points defined when you hover them while moving a shape.
What I tried is to save anchors points inside an array and when the shape is dropped, I draw the shape on the closest anchor point.
Easeljs is my main js lib so I want to keep it but if needed I can use an other one with easeljs.
Thanks in advance for your help!
This is pretty straightforward:
Loop over each point, and get the distance to the mouse
If the item is closer than the others, set the object to its position
Otherwise snap to the mouse instead
Here is a quick sample with the latest EaselJS: http://jsfiddle.net/lannymcnie/qk1gs3xt/
The distance check looks like this:
// Determine the distance from the mouse position to the point
var diffX = Math.abs(event.stageX - p.x);
var diffY = Math.abs(event.stageY - p.y);
var d = Math.sqrt(diffX*diffX + diffY*diffY);
// If the current point is closeEnough and the closest (so far)
// Then choose it to snap to.
var closest = (d<snapDistance && (dist == null || d < dist));
if (closest) {
neighbour = p;
}
And the snap is super simple:
// If there is a close neighbour, snap to it.
if (neighbour) {
s.x = neighbour.x;
s.y = neighbour.y;
// Otherwise snap to the mouse
} else {
s.x = event.stageX;
s.y = event.stageY;
}
Hope that helps!
I have written a small 2D game in javascript that uses a grid where the player starts at position [0,0] and can move an almost infinite distance in either direction.
Now I want to implement A* pathfinding, but I'm having some problems finding the best way to store the world with all it's different obstacles, enemies and terrain. This is what I have tried or thought about so far.
Array of arrays
Here I store the world in an array of arrays [x][y].
var world = [[]];
world[312][11] = 0;
world[312][12] = 0;
world[312][13] = 1;
world[312][14] = 1;
...
This works great with A* pathfinding! It's easy and very fast to access a specific coordinate and populate the world. In the example above I just store passable (0) or impassable (1) terrain, but I can store pretty much whatever I want there. However, this doesn't work very well with negative coordinates like if my players is at [-12][-230]. Negative keys in a javascript array isn't actually part of the array, they won't be included in world.length or world[3].length and from what I understand, it's overall bad practice and might have some impact on the performance as well. I read somewhere that if you are using negative keys in your array, you are doing it wrong.
I would still not pass the entire world into the A* function for obvious reasons. Just a small part close to my player, but the coordinates would correspond to the positions in the array which is easy to work with.
A separate array of arrays just for A* pathfinding
This is where I'm at right now. I have a separate 50x50 grid called pathMap = [[]], that is only used for pathfinding.
var pathMap = [[]];
pathMap[0][0] = 0;
pathMap[0][1] = 0;
pathMap[0][2] = 1;
pathMap[0][3] = 1;
...
It starts at pathMap[0][0] and goes to pathMap[50][50] and is working as an overlay on my current position where I (as the player) will always be in the center position. My real coordinates may be something like [-5195,323], but it translates to pathMap[25][25] and everything close to me is put on the pathMap in relation to my position.
Now this works, but it's a huge mess. All the translations from one coordinate to another back and forth makes my brain hurt. Also, when I get the path back from A*, I have to translate each step of it back to the actual position my element should move to in the real world. I also have to populate the same object into 2 different grids every update which hurts performance a bit as well.
Array of objects
I think this is where I want to be, but I have some issues with this as well.
var world = [];
world[0] = { x: -10, y: 3, impassable: 0 };
world[1] = { x: -10, y: 4, impassable: 0 };
world[2] = { x: -10, y: 5, impassable: 1 };
world[3] = { x: -10, y: 6, impassable: 1 };
...
Works great with negative x or y values! However, it's not as easy to find for instance [10,3] in this array. I have to loop through the entire array to look for an object where x == 10 and y == 3 instead of the very easy and fast approach world[10][3] in the first example. Also, I can't really rely on the coordinates being in the right order using this version, sorting becomes harder, as does other things that was a lot easier with the array of arrays.
Rebuild the game to always be on the positive side
I would prefer not to do this, but I have considered placing the players starting position at something like [1000000,1000000] instead, and making negative coordinates off limits. It seems like a failure if I have to remove the vision I have of endlessness just to make the pathfinding work with less code. I know there will always be some upper or lower limits anyways, but I just want to start at [0,0] and not some arbitrary coordinate for array related reasons.
Other?
In javascript, is there another option that works better and is not described above? I'm very open to suggestions!
Is there a best practice for similar cases?
You have three coordinates system you must distinguish :
the world coordinates.
the world model / path-finding (array) coordinates.
the screen coordinates.
The screen coordinates system depends upon :
the viewport = the canvas. (width, height in pixels).
a camera = (x,y) center in world coordinates + a viewWidth (in world coordinates).
To avoid headaches, build a small abstraction layer that will do the math for you.
You might want to use Object.defineProperty to define properties, that will provide a fluent interface.
var canvas = ... ;
var canvasWidth = canvas.width;
var canvasHeigth = canvas.heigth;
var world = {
width : 1000, // meters
height : 1000, // meters
tileSize : 0.5, // height and width of a tile, in meter
model : null, // 2D array sized ( width/tileSize, XtileSize )
};
// possibles world coordinates range from -width/2 to width/2 ; - height/2 height/2.
var camera = {
x : -1,
y : -1,
viewWidth : 10, // we see 10 meters wide scene
viewHeight : -1 // height is deduced from canvas aspect ratio
};
camera.viewHeight = camera.viewWidth * canvasWidth / canvasHeight ;
Then your character looks like :
// (x,y) is the center of the character in centered world coordinates
// here (0,0) means (500,500) world coords
// (1000,1000) array coords
// (320, 240) screen coords in 640X480
function /*Class*/ Character(x, y) {
var _x=x;
var _y=y;
var _col=0;
var _row=0;
var _sx=0.0;
var _sy=0.0;
var dirty = true;
Object.defineProperty(this,'x',
{ get : function() {return _x; }
set : function(v) { _x=v;
dirty=true; } });
Object.defineProperty(this,'x',
{ get : function() {return _y; }
set : function(v) { _y=v;
dirty=true; } });
Object.defineProperty(this,'col',
{ get : function() {
if (dirty) updateCoords();
return _col; } });
Object.defineProperty(this,'row',
{ get : function() {
if (dirty) updateCoords();
return _row; } });
Object.defineProperty(this,'sx',
{ get : function() {
if (dirty) updateCoords();
return _sx; } });
Object.defineProperty(this,'sy',
{ get : function() {
if (dirty) updateCoords();
return _sy; } });
function updateCoords() {
_row = ( ( _x + 0.5 * world.width )/ world.tileSize ) | 0 ;
_col = ( ( _x + 0.5 * world.height )/ world.tileSize ) | 0 ;
_sx = canvasWidth * ( 0.5 + ( _x - camera.x ) / camera.viewWidth ) ;
_sy = canvasHeight * ( 0.5 + ( _y - camera.y ) / camera.viewHeight ) ;
dirty = false;
}
}
I am trying to implement line sweep algorithm by using PURE Javascript (no other frameworks) that basically scans through the screen from left to right and looks all the elements (including overlapped elements) that share the same x coordinate.
For example
I have 6 div elements with black border, and they all layout randomly on the screen. for illustration purposes I am using a vertical dotted blue line to scan through across the plane from left to right. The goal is that report all the elements that the line passed over . For the example above, how do we report that Div A, Div E, Div D and also the hyperlink D within Div D by using JavaScript ?
You can get the position of elements with the getBoundingClientRect method. Then loop over them and check whether they match your scanning:
var all = document.body.getElementsByTagName("*");
var x = /* blue line */;
var match = [];
for (var i=0; i<all.length; i++) {
var rect = all[i].getBoundingClientRect();
if (rect.left < x && rect.right > x)
match.push(all[i]);
});
Shorter, functional way:
var match = Array.prototype.filter.call(document.body.querySelectorAll("*"), function(el) {
var rect = el.getBoundingClientRect();
return rect.left < x && rect.right > x;
});
If you need a quick-access function for often use, you would store all elements (with their coordinates) in a sorted data structure, a segment tree, where you can search for them.
Also, when it is guaranteed that child nodes of DOM elements do not exceed their parents boundaries, you can easily use the DOM itself as a search tree:
var x = /* the blue line */;
var match = function find(el, set) {
var rect = el.getBoundingClientRect();
if (rect.left < x && rect.right > x) {
set.push(el);
for (var i=0; i<el.children.length; i++)
find(el.children[i]);
}
return set;
}(document.body, []);
Gather all of the DOM elements
Find their positions and stash them in an array (so you can use the data again without going through the DOM)
Loop through the array and solve for x
Is there someway to get the visible area of the panel when zooming. I
have a force directed graph and I am interested in obtaining all the
elements that are in the visible area after a zoom event.
Any suggestions?
Thanks
You can access the current transformation matrix of a panel via the transform parameter. So, in the following example:
var vis = new pv.Panel()
.width(200)
.height(200);
var panel = vis.add(pv.Panel)
.event("mousewheel", pv.Behavior.zoom(1))
.fillStyle('#ccc');
var dot = panel.add(pv.Dot)
.data([[25,25],[25,75],[75,25],[75,75]])
.top(function(d) d[0])
.left(function(d) d[1])
.size(30)
.fillStyle('#999');
vis.render();
If you load this example, then zoom around a bit, you can access the current transformation matrix like this:
var t = panel.transform(),
tk = t.k, // scale factor, applied before x/y
tx = t.x, // x-offset
ty = t.y; // y-offset
You should be able to determine whether a child mark (e.g., in this example, dot) is in the visible area by applying the transformation matrix to its top and left parameters and then checking to see whether they're within the panel's original bounding box (0,0,200,200). For the dot above, you could check like this:
function(d) {
var t = panel.transform(),
// assuming the current dot instance is accessible as "this"
x = (this.left() + t.x) * t.k, // apply transform to dot x position
y = (this.top() + t.y) * t.k; // apply transform to dot y position
// check bounding box. Note that this is a little simplistic -
// I'm just checking dot center, not edges
return x > panel.left() && x < (panel.left() + panel.width()) &&
y > panel.top() && y < (panel.top() + panel.height());
}