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!
Related
I want to animate a curved motion (no rotation) of an object by using svg.js. But I can't find any easy solution for this problem. I wrote two little functions which work fine, but it isn't working like a normal animation, and it doesn't run perfectly in the background.
I would prefer some solution like this:
var draw = SVG("drawing").size(500,500);
var rect = draw.rect(50,50);
rect.animate().curvedmove(100,100);
The two functions I made:
function animateJump(object,start,end,ampl,y,i=0){
var speed = 25;
var pos = 0;
pos = start+i*((end-start)/speed);
object.animate(1).move(pos,y+bounceFunction(start,end,ampl,pos));
if (i <= speed){
animateJump(object,start,end,ampl,y,i+1)
}
}
function bounceFunction(a,b,c,x){
return -1 * (x-a)*(x-b) * c * (4/((a-b)*(b-a)));
}
Is there some easy solution?
Thanks for any help!
The animate method establish a new animation context in which runs the timer you specified (1 sec by default). So if you do el.animate().move(100,100) the element will move to position 100,100 in 1 second.
However, if you want to use your own function you need to listen to the during event which gives you the current position from 0-1 in time.
el.animate().during(function(pos, morphFn, easedPos) {
this.move(pos, bounceFunction(pos))
})
Note that pos is a value between 0 and 1 so setting it directly as coordinate does not make that much sense. You need to figure our the start and end value of the move and calculate it yourself (or use the morphFn like morphFn(start, end))
Example:
var startX = 100
var endX = 300
var startY = 100
var endY = 300
el.animate().during(function(pos, morphFn, easedPos) {
var x = morphFn(startX, endX)
var y = SVG.morph(bounceFunction(pos))(startY, endY)
this.move(x, y)
})
the morphFn is by default bound to the current position. So if you have your own position (like when using your custom bounce function) you need to create a new morph function which you can do with the SVG.morph method. This method expects a position and gives back a morph function bound to this positon.
So this would be the same:
var x = SVG.Morph(pos)(startX, endX)
var y = SVG.Morph(bounceFunction(pos))(startY, endY)
I wanted to make a snapping functionality to snap to my mesh vertices. I experimented with several solutions.
One solution is to add THREE.Sprite instances for all vertices in my scene and then using a rayCaster to decide whether there is a snap point in the intersects array. It works pretty well; here is a fiddle with a demo.
The idea is to hide the sprites in the final solution so they won't be rendered, but my scenes are pretty big so it would still mean adding lots of sprites to my scene (for every vertex one so possibly thousands of sprites) to detect snap points with my rayCaster.
var intersects = rayCaster.intersectObject(scene, true);
var snap = null;
if (intersects.length > 0) {
var index = 0;
var intersect = intersects[index];
while (intersect && intersect.object.name === 'snap') {
snap = sprite.localToWorld(sprite.position.clone());
index++
intersect = intersects[index];
}
if (intersect) {
var face = intersect.face;
var point = intersect.point;
var object = intersect.object;
mouse3D.copy(point);
}
}
if (snap) {
renderer.domElement.style.cursor = 'pointer';
} else {
renderer.domElement.style.cursor = 'no-drop';
}
I also thought of an alternative solution by doing the math using results from the rayCaster. That solution is demonstrated in this fiddle.
The idea here is to test all vertices from the geometry of the object (mesh) that is intersected and then check whether the distance between the intersect point and those vertices from the geometry is smaller then the snap threshold.
var intersects = rayCaster.intersectObject(mesh, true);
if (intersects.length > 0) {
var distance, intersect = intersects[0];
var face = intersects[0].face;
var point = intersects[0].point;
var object = intersects[0].object;
var snap = null;
var test = object.worldToLocal(point);
var points = object.geometry.vertices;
for (var i = 0, il = points.length; i < il; i++) {
distance = points[i].distanceTo(test);
if (distance > threshold) {
continue;
}
snap = object.localToWorld(points[i]);
}
if (snap) {
sphereHelper.position.copy(snap);
sphereHelper.visible = true;
renderer.domElement.style.cursor = 'pointer';
} else {
sphereHelper.visible = false;
renderer.domElement.style.cursor = 'no-drop';
}
}
The sad thing is that in the second solution snap will only work when the mouse is moved from the surface of the intersected object towards a vertex. In case the mouse is moved from outside the object (so there is no intersection) the snapping won't work. In that respect the first solution with sprites is much more usable...
My question, am I overcomplicating things and is there a better/simpler/more efficient way to do this? Any suggestions for alternative approaches are welcome.
I looked into #meepzh his suggestion of using an octree and made the following solution using this threeoctree repository from github. The THREE.Octree class did not solve all my problems out-of-the-box so
I added custom method findClosestVertex to the THREE.Octree class that can be used like this.
var snap = octree.findClosestVertex(position, radius);
snap is null in case no vertices within the radius of position and returns the closest point (THREE.Vector3) in world space otherwise.
I made a Pull-Request here on github for the new method.
Here is a demo in a fiddle
I have a sphere (globe) with objects (pins) on the surface with DOM elements (labels) what are calculated from the pin position to 2d world.
My problem is that when the pins go behind the globe (with mouse dragging or animation) then I need to hide labels which are in DOM so that the text label isn’t visible without the pin.
My logic is that if I can get the pin which is in 3D world to tell me if it’s behind the globe then I can hide the label associated with the pin.
Codepen with whole the code.
The function that I have researched together:
function checkPinVisibility() {
var startPoint = camera.position.clone();
for (var i = 0; i < pins.length; i++) {
var direction = pins[i].position.clone();
var directionVector = direction.sub(startPoint);
raycaster.set(startPoint, directionVector.clone().normalize());
var intersects = raycaster.intersectObject(pins[i]);
if (intersects.length > 0) {
// ?
}
}
}
I have researched through many posts but can’t really get the result needed:
ThreeJS: How to detect if an object is rendered/visible
Three.js - How to check if an object is visible to the camera
http://soledadpenades.com/articles/three-js-tutorials/object-picking/
I have gotten it work by mouse XY position as a ray, but can’t really get a working solution with constant rendering for all the pins.
You want to know which points on the surface of a sphere are visible to the camera.
Imagine a line from the camera that is tangent to the sphere. Let L be the length of the line from the camera to the tangent point.
The camera can only see points on the sphere that are closer to the camera than L.
The formula for L is L = sqrt( D^2 - R^2 ), where D is the distance from the camera to the sphere center, and R is the sphere radius.
WestLangley's solution in code form. Please give him the accepted answer if you feel his answer the best.
function checkPinVisibility() {
var cameraToEarth = earth.position.clone().sub(camera.position);
var L = Math.sqrt(Math.pow(cameraToEarth.length(), 2) - Math.pow(earthGeometry.parameters.radius, 2));
for (var i = 0; i < pins.length; i++) {
var cameraToPin = pins[i].position.clone().sub(camera.position);
if(cameraToPin.length() > L) {
pins[i].domlabel.style.visibility = "hidden";
} else {
pins[i].domlabel.style.visibility = "visible";
}
}
}
Oddly enough it is still susceptible to that camera pan error. Very weird, but it's still better than my Projection-onto-LOOKAT solution.
MY OLD ANSWER:
I would have assumed its something like this, but this doesn't seem to work as expected.
if (intersects.length > 0) {
pins[i].domlabel.style.visibility = "visible";
} else {
pins[i].domlabel.style.visibility = "hidden";
}
I got close with this solution, but its still not perfect. What the code below does is it finds the distance along the LOOKAT direction of the camera to a pin (cameraToPinProjection) and compares it with the distance along the LOOKAT direction to the earth (cameraToEarthProjection).
If cameraToPinProjection > cameraToEarthProjection it means the pin is behind the centre of the earth along the LOOKAT direction (and then I hide the pin).
You will realise there's a "0.8" factor I multiply the cameraToEarth projection by. This is to make it slightly shorter. Experiment with it.
Its not perfect because as you rotate the Earth around you will notice that sometimes labels don't act the way you'd like them, I'm not sure how to fix.
I hope this helps.
function checkPinVisibility() {
var LOOKAT = new THREE.Vector3( 0, 0, -1 );
LOOKAT.applyQuaternion( camera.quaternion );
var cameraToEarth = earth.position.clone().sub(camera.position);
var angleToEarth = LOOKAT.angleTo(cameraToEarth);
var cameraToEarthProjection = LOOKAT.clone().normalize().multiplyScalar(0.8 * cameraToEarth.length() * Math.cos(angleToEarth));
var startPoint = camera.position.clone();
for (var i = 0; i < pins.length; i++) {
var cameraToPin = pins[i].position.clone().sub(camera.position);
var angleToPin = LOOKAT.angleTo(cameraToPin);
var cameraToPinProjection = LOOKAT.clone().normalize().multiplyScalar(cameraToPin.length() * Math.cos(angleToPin));
if(cameraToPinProjection.length() > cameraToEarthProjection.length()) {
pins[i].domlabel.style.visibility = "hidden";
} else {
pins[i].domlabel.style.visibility = "visible";
}
}
}
I'm developing a hex-grid strategy game in HTML5, and since it becomes complicated to add more buttons to UI, I'm rewriting my code using KineticJS to draw the canvas.
In my game, there is a minimap, and a rectangle in it showing the position of player's camera. When the player clicks on the minimap, the center of his camera will be set to the position he clicked. My original code does not use any external library, and it looks like this:
this.on('click', function(event){
var canvas = document.getElementById('gameCanvas');
var x = event.pageX - canvas.offsetLeft;
var y = event.pageY - canvas.offsetTop;
camera.setPos( // calculate new position based on x and y....);
}
})
So basically what I want is the POSITION OF CLICK EVENT RELATIVE TO THE CANVAS. Since now KineticJS takes control of the canvas, and it does not let me set canvas id, I can't use getElementById to choose my canvas anymore. Is there another way to do it?
There might be some way I did not figure out that can set the canvas id, but I'm expecting a more elegent solution, idealy through KineticJS API.
Thanks.
To get the mouse position on the stage (canvas), you can use:
var mouseXY = stage.getPointerPosition();
var canvasX = mouseXY.x;
var canvasX = mouseXY.y;
You can get the mouse position inside a shape click the same way:
myShape.on('click', function() {
var mouseXY = stage.getPointerPosition();
var canvasX = mouseXY.x;
var canvasY = mouseXY.y;
});
Good question, and I think what you are looking for is getAbsolutePosition().
The following is useful things to know from my experience.
getPosition()
node x/y position relative to parent
if your shape is in layer, x/y position relative to layer
if your shape is in group, x/y position relative to group
getParent()
To know what parent node is
getX()
x position relative to parent
getY()
y position relative to parent
getAbsolutePosition()
absolute position relative to the top left corner of the stage container div
Few more useful things,
getScale()
if your stage is zoomed in or out, better to know this.
Everything is here in document, http://kineticjs.com/docs/symbols/Kinetic.Node.php
This is an old question, but as I can see, the best answer does not take into account the rotation.
So here it is my solution based on Kinetic v5.1.0. who take into account all transformation.
var myStage = new Kinetic.Stage();
var myShape = new Kinetic.Shape(); // rect / circle or whatever
myShape.on('click', function(){
var mousePos = myStage.getPointerPosition();
var p = { x: mousePos.x, y: mousePos.y }; // p is a clone of mousePos
var r = myShape.getAbsoluteTransform().copy().invert().point(mousePos);
// r is local coordinate inside the shape
// mousePos is global coordinates
})
I found out the best way to do it is...
var bgxy = bg.getAbsolutePosition();
var x = event.pageX - stage.getContainer().offsetLeft - bgxy.x;
var y = event.pageY - stage.getContainer().offsetTop - bgxy.y;
You should use event.layerX and event.layerY :
this.on('click', function(event){
var canvas = document.getElementById('gameCanvas');
var x = event.layerX;
var y = event.layerY;
camera.setPos( // calculate new position based on x and y....);
}
})
I have HTML 5 circle drag and drop example,
http://jsfiddle.net/eGjak/503/
i want to follow things
prevent drag circles outside of the canvas
hide lines over the circles
prevent drag over another circle
i play with some codes but no luck there. can anyone please help me, by logic or some helpful resource
This is an easy one. It's simply checking x doesn't leave the left or right side of the screen and y doesn't leave the top or bottom of the screen
if (x>0 || x<(canvas.width - circle.width)
&& y>0 || y<(canvas.height - circle.height)) {
...update...
}
Here you need to do a line/circle collision check. See here.
For this you need circle/circle collision detection. The below will return true if collided otherwise false:
this.isIntersecting = function(c1center, c1radius, c2center, c2radius)
{
var dX = Math.pow(c1center.x - c2center.x, 2);
var dY = Math.pow(c1center.y - c2center.y, 2);
var r2 = Math.pow(c1radius.radius() + c2radius.radius(), 2);
return (dX + dY <= r2);
}
c1center and c2center are object with x, y properties (eg: c1center = {x:0, y:0 })