I have an application with many draggable objects that can also be rotated in 90 degree increments. I'm trying to figure out how to stop the user from dragging the objects outside the Raphael paper (canvas).
This is fairly simple for unrotated objects. I can simply see if the current x and y coordinates are less than 0 and set them to 0 instead. I can adjust similarly by checking if they are outside the canvas width and height.
However, a problem arises when the object is rotated because for some odd reason the coordinate plane rotates as well. Is there an easy way to keep objects inside the canvas? Or is there an example of some this somewhere?
I have spent many hours fiddling with this and I can't seem to make sense of the rotated coordinate plane in order to adjust my calculations. Even when debugging the current coordinates, they seem to shift oddly if I drag an object, release it, and then drag the object again.
Any help is greatly appreciated.
Thanks,
Ryan
I had a similar problem, I needed to move a shape within the boundaries of another shape, so what I did was:
element.drag(onstart, onmove, onend);
...
onStart: function(x,y,e){
// Initialize values so it doesn't recalculate per iteration
// this allows to resume dragging from the point it were left
App.oldX = 0;
App.oldY = 0;
App.currentCircleX = App.fingerPath.attr('cx');
App.currentCircleY = App.fingerPath.attr('cy');
},
onMove: function(dx,dy,x,y,e){
App.setDirection(dx,dy);
},
onEnd: function(e){
// nothing to do here for now
},
// this function tells the element to move only if it's within the bound area
setDirection: function(dx, dy){
var isXYinside;
this.newX = this.currentCircleX - (this.oldX - dx);
this.newY = this.currentCircleY - (this.oldY - dy);
// HERE is the key, this method receives your bounding path and evaluates the positions given and then returns true or false
isXYinside = Raphael.isPointInsidePath(this.viewportPath, this.newX, this.newY);
this.oldX = dx;
this.oldY = dy;
// so if it is within the bound area, will move, otherwise will just stay there
if (isXYinside) {
this.fingerPath.attr({
"cx": this.newX,
"cy": this.newY
});
this.currentCircleX = this.newX;
this.currentCircleY = this.newY;
}
}
I know this is an old one, but I stumbled upon this question when trying to figure out a way to do it. So here's my 2 cents in case someone has this problem.
Reference:
Raphael.isPointInsidePath
Have you tried Element.getBBox()
There Are 2 flavones which give the result before rotation and after rotation
You should toggle the Boolean argument and test it
Related
I'm think I'm having a similar issue as this in that I can not work out (or know if it exists) whereby I can get access to the scaling applied to a given object (in my instance, a raster).
I need to know this so I can animate the scaling via Tween.js.
Anyone have any ideas or know if indeed it is possible to find out the current scaling applied to a raster (or any) object?
I thought it was an issue with Rasters so I tried tweening the scale property of a Path and then a Group and I couldn't get access to the values in order to animate it.
Because I am using Tween.js I can not simply use the object.scale(value) function.
UPDATE
I even tried applying an arbitrary (animated) number to the scale function and it failed to work... i.e.:
object.scale( 0 );
object.arbitraryNumber = 0;
createjs.Tween.get( object )
.to( { arbitraryNumber:1 } , 1000, createjs.Ease.getPowInOut(2) )
.addEventListener( "change", function( event ) {
event.target.target.scale( event.target.target.arbitraryNumber);
} );
Although this did not work, when the same approach was applied to the x position of the object, it animated fine.
Is there anything that needs to be flagged in order to update scaling of an object?
When calling Item.scale() method on each frame with values from 0 to 1, you are actually scaling down item exponentially because each call scales the item relatively to the previous value.
What you want to do is animate the Item.scaling property instead.
You also have to know that by default, PaperJS use global coordinates system and apply every transformations directly to points.
You can change this behavior by setting Item.applyMatrix property to false.
Doing this, scale change will affect item matrix instead of affecting points coordinates and you will be able to animate it as you expect.
Here is simple Sketch of a scale animation:
var circle = new Path.Circle(view.center, 50);
circle.fillColor = 'orange';
circle.applyMatrix = false;
function onFrame(event)
{
circle.scaling = Math.sin(1 + event.count * 0.05);
}
You should be able to transpose this example to your Tween.js context easily.
Trying to rotate an object around any axis.
For example like a door hinge (on edge of object) or planet around the sun (outside of object).
The problem seems to be defining the axis. The below unit vector results in axis remaining on object's origin (centre) therefor identical to standard rotation:
object2.rotateOnAxis(new THREE.Vector3(1,0,0), 0.01);
// same as
object1.rotation.x += 0.01;
See code example: JSFiddle
EDIT: Looking for a way that one can rotate around a pivot without using nested children. Rotating a child's parent provides an easy way to manipulate the child's pivot point, but modifying the pivot point is not viable.
Example below, if you wanted to rotate the cube in a figure 8 motion, it would be achievable with this method by changing the parent. But one would have to assure that the new parent's position and orientation is precisely configured to make the child seamlessly jump between parents, and complex motions that do not repeat or loop would be very complicated. Instead, I would like to (and I will paraphrase the question title) rotate an object on a specific axis without using object nesting anywhere in the scene, including outside of the object's mesh.
See code example: JSFiddle with pivots
If you want to rotate an object around an arbitrary line in world space, you can use the following method. The line is specified by a 3D point and a direction vector (axis).
THREE.Object3D.prototype.rotateAroundWorldAxis = function() {
// rotate object around axis in world space (the axis passes through point)
// axis is assumed to be normalized
// assumes object does not have a rotated parent
var q = new THREE.Quaternion();
return function rotateAroundWorldAxis( point, axis, angle ) {
q.setFromAxisAngle( axis, angle );
this.applyQuaternion( q );
this.position.sub( point );
this.position.applyQuaternion( q );
this.position.add( point );
return this;
}
}();
three.js r.85
I'm being driven mildly insane looking for a working combination of interactions. I basically need to make something like a google earth style setup, where you can:
orbit round an object, highlighting the centre-most location,
click a menu link and animate rotation of the object to a particular 'location' (highlighting the new location).
I'm using orbitcontrols for the first bit, and was hoping to tween the orbitcontrols directly for the menu link bit, but couldn't get the camera to move in the right path. SO I put the camera inside an object, and whilst orbitcontrols handles the camera, the tweening is done on the object ('camHolder') instead.
So there are two moving parts (cam controlled by user's mouse, camHolder tweened into position by link clicks), and when either one moves, the rotational difference between them changes. In order to highlight the right 'point' between these two rotation values, I need to keep track of the offset between the two. Basically (simplified version of the codepen):
// ------- MOUSE/CAMERA INTERACTION ---------
// location of points (in radians):
var pointLongs=[-3,-2,-2.5,-2,-1.5,-1,-0.5,0,1,2,2.5,3];
// most recent point highlighted (by menu click):
var currentPoint = 5;
// get diff (in radians) between camera and current point
var pointDistance = pointLongs[currentPoint] - camera.rotation.y;
// the offset rotation of cam (i.e. whats closest to the front):
var offset = camera.rotation.y + pointDistance;
// find the closest value to offset in pointLongs array:
var closest = pointLongs.reduce(function (prev, curr) {
return (Math.abs(curr - offset) < Math.abs(prev - offset ) ? curr : prev);
});
closestPointIndex = pointLongs.indexOf(closest);
// highlight that point (raise it up):
scene.getObjectByName(pointNames[closestPointIndex]).position.y = 20;
This seems to work as long as pointDistance is above 0, but if not, the tracking of the current 'point' only works on part of the mouse orbiting circle, when it should work all the way round.
Codepen here: http://codepen.io/anon/pen/BNPWya (the Sole tween code is embedded in there so skip the first chunk...). Try rotating the shape with the mouse, and notice that the points aren't raised all the way around. Click the random / next menu buttons, and the 'gap' changes... Sometimes it does go all the way round!
I've tried changing just about all the values (pointLongs all positive values; initial rotation of camera, etc) but my maths is generally terrible, and I've lost the ability to see straight - anyone have any ideas? Please ask if something doesn't make sense!
I'd add the tag 'HelpMeWestLangleyYoureMyOnlyHope' but I don't have enough reputation :D
TLDR; rotation of object and camera won't 'sync', need to either correct the difference, or maybe find a way to tween position/rotation of orbitcontrols?
This question already has answers here:
Zoom Canvas to Mouse Cursor
(5 answers)
Closed 8 years ago.
Is there a way to zoom on the cursor point using this code? I can't get my head around to doing it. The canvas zooms but it only zooms in and out from the top left corner.
var previousMousePosition = new Vector(0, 0);
function OnMouseWheel (event) {
var delta = event.wheelDelta ? event.wheelDelta/40 : event.detail ? -event.detail : 0;
var mousePosition = new Vector(event.clientX, event.clientY);
var scaleFactor = 1.1;
if (delta) {
var factor = Math.pow(scaleFactor,delta);
context.scale(factor,factor);
}
}
The canvas always scales from it current origin. The default origin is [0,0].
If you want to scale from another point, you can first do context.translate(desiredX,desiredY);. This will reset the origin of the canvas to [desiredX,desiredY].
That way your context.scale will scale from your specified origin.
Since all context transformations remain in effect for every subsequent drawing, you often want to reverse the transformations after you are done with the current drawing (=='resetting' for the next drawing which might/might not use the current transformations). To untransform, just call the transformation with negative arguments: eg. context.scale(-factor,-factor). Transformations should be done in reverse order from their original transformations.
So your refactored code could be:
// set the origin to mouse x,y
context.translate(mousePosition.x,mousePosition.y);
// scale the canvas at x,y
context.scale(factor,factor);
// ...draw stuff
// reverse the previous scale
context.scale(-factor,-factor);
// reverse the previous translate
context.translate(-mousePosition.x,-mousePosition.y);
First, I'd like to point out that from your code it looks like you're listening for 'mousewheel' events on the canvas. As noted here, the 'mousewheel' event is non-standard and is not on track to become a standard. As a result, you'll get results that are, at best, mixed when listening for it. The 'scroll' event is available on nearly every platform, and will likely be a better avenue for capturing user input.
As far as your question, you're on the right track for the behavior that you're looking for, but you're missing one step.
When you call scale on a canvas context object, the behavior is very simple. Starting at the top left corner (0,0), the method scales the points of the canvas by the factors provided. Say you have a 10x10 canvas, and a black dot at 1,1. If the canvas is scaled by a factor of 2 on both axes, the 0,0 point will stay in the same place but point 1,1 will be where the point 2,2 was before scaling.
In order to achieve the 'zooming' behavior you're looking for, the context has to be translated after scaling so that the reference point occupies the same physical position it did before the scaling. In your case, the reference point is the point where the user's cursor sits when the zoom action is performed.
Luckily, the canvas context object provides a translate(x,y) method that moves the origin of the context relative to the 0,0 point of the canvas. To translate it the correct ammount, you have to:
Calculate the distance of the mouse cursor from the canvas origin before zooming
Divide that distance by the scaling factor
Translate the origin by that value
Since your code doesn't indicate the structure of your HTML, below I've marked it up with some comments and pseudocode to show how you might implement this algorithm:
//You'll need to get a reference to your canvas in order to calculate the relative position of
//the cursor to its top-left corner, and save it to a variable that is in scope inside of your
//event handler function
var canvas = document.getElementById('id_of_your_canvas');
//We're also going to set up a little helper function for returning an object indicating
//the position of the top left corner of your canvas element (or any other element)
function getElementOrigin(el){
var boundingBox = el.getBoundingClientRect();
return { x: boundingBox.left, y: boundingbox.top};
}
function OnMouseWheel (event) {
//you probably want to prevent scrolling from happening or from bubbling so:
event.preventDefault();
event.stopPropagation();
var delta = event.wheelDelta ? event.wheelDelta/40 : event.detail ? -event.detail : 0;
var canvasCorner = getElementOrigin(canvas)
//JavaScript doesn't offer a 'vector' or 'point' class natively but we don't need them
var mousePosition = {x: event.clientX, y: event.clientY};
var diff = {x: mousePostion.x - canvasCorner.x, y: mousePosition.y - canvasCorner.y};
var scaleFactor = 1.1;
if (delta) {
var factor = Math.pow(scaleFactor,delta);
var transX = (-1/factor) * diff.x;
var transY = (-1/factor) * diff.y;
context.scale(factor,factor);
context.translate(transX, transY);
}
}
I have done lots of examples on dragging an object created by Raphael's library. Now I am working with sets and was also able to write a code to drag them.
Now my problem appeared when I rotate an object and then drag it.
Check out this code example: demo
var paper = Raphael('stage', 300, 300);
var r = paper.rect(50,100,30,50).attr({fill:"#FFF"}).rotate(45),
t = paper.text(30, 140, "Hello");
var p = paper.set(r, t);
r.set = p, t.set = p;
p.newTX=0,p.newTY=0,p.fDx=0,p.fDy=0,p.tAddX,p.tAddY,p.reInitialize=false,
start = function () {
},
move = function (dx, dy) {
var a = this.set;
a.tAddX=dx-a.fDx,a.tAddY=dy-a.fDy,a.fDx=dx,a.fDy=dy;
if(a.reInitialize)
{
a.tAddX=0,a.fDx=0,a.tAddY=0;a.fDy=0,a.reInitialize=false;
}
else
{
a.newTX+=a.tAddX,a.newTY+=a.tAddY;
a.attr({transform: "t"+a.newTX+","+a.newTY});
}
},
up = function () {
this.set.reInitialize=true;
};
p.drag(move, start, up);
By examining the DEMO you can see that the set is created with rotated rectangle, but as soon you drag it, it goes back to the 0 degree state. Why? Any solutions?
The problem is that whenever an element is transformed by applying a string containing instructions to move, rotate, scale etc, it resets the transformation object, and hence previous transformations get lost. To avoid this, add "..." at the beginning of the transformation string. Like,
var el = paper.rect(10, 20, 300, 200);
// translate 100, 100, rotate 45°, translate -100, 0
el.transform("t100,100r45t-100,0");
// NOW, to move the element further by 50 px in both directions
el.transform("...t50,50");
If "t50,50" is used instead of "...t50,50", then transformation effect for "t100,100r45t-100,0" is lost and transformation effect for "t50,50" rules.
Raphael reference for further study: http://raphaeljs.com/reference.html#Element.transform
Hope this helps.
I found an easy solution to this problem. Since I need to have a diamond instead of rectangle, I have created a path that represents that diamond. Then this path becomes just like a square 45 degree rotated.
This turned out to be easy because dragging functionality I had for my program works perfectly with paths.