I'm trying to scale and then rotate a triangle and then translate it to a given point in Snap SVG.
I want to rotate the triangle around the top of it not the center, so i can build something like a pie.
So I thought I scale first, then rotate and later translate.
var t = new Snap.Matrix();
t.scale(0.5);
t.rotate(45, bbox.cx, (bbox.cy-(bbox.h/2)));
But the scale and rotation somehow are allways a bit off.
I reused a jsfiddle I found and updated it, so you can see what I try:
http://jsfiddle.net/AGq9X/477/
Somehow the bbox.cx and bbox.cy are not in the center of the triangle.
On my local setup they are.
The strange thing is, just rotation without scaleing works fine,
but scaling and then roation always seems to be a bit off on the y axis, the triangle doesn't stays at the rotation point.
Any ideas how i can fix that?
EDIT:
Ok I found the Solution,thanks to lan, you were right, the center of scaleing is important, and
I thought it was useing the center of the object, but it was the upper left corner. I adjusted it
and now it works greate:
var bbox = obj.getBBox(); //get coords etc. of triangle object
var t = new Snap.Matrix();
var offset = (bbox.cy+(bbox.h)) - centerY; //translate Y to center,
//depends on scaleing factor (0.5 = bbox.h, 0.25 = bbox.h*2)
t.scale(0.5, 0.5, bbox.cx, (bbox.cy+(bbox.h/2))); //scale object
t.translate(0,-offset); //translate to center
t.rotate(45, bbox.cx, (bbox.cy+(bbox.h/2))); //rotate object
obj.transform(t); //apply transformation to object
EDIT2:
I wanted to know how to save transformation, so you don't need to apply them every time you use a new transformation. Ian recommended to use element.transform() like so to get the old transformations:
element.transform( element.transform() + 's2,2' )
This is slightly more complicated than one would expect, but you would be animating a matrix, which does some odd things sometimes.
Personally I would use Snaps alternate animate method Snap.animate() and not using a matrix. Set the scale first and then build your animation string.
Something like..
var triangle2 = p.select("#myShape2").transform('s0.5');
...
Snap.animate(0,90,function( val ) {
triangle2.transform('r'+ val + ',' + bbox.cx+','+(bbox.cy-(bbox.h/2))+'s0.5')
}, 2000)
jsfiddle
Related
I'm currently working on a mini map for a game in which keeps track of different items of importance on and off the screen. When I first created the mini map through a secondary camera rendered onto a texture and displayed on screen in a miniature display, it was rectangle shape. I was able to ensure when the item of importance left the view of the map, an arrow pointing to the target showed up and remained on the edge of the map. It was basically clamping the x & y positions of the arrow to half the camera view's width and length (with some suitable margin space).
Anyway. Now I am trying to make the mini map circular and while I have the proper render mask on to guarantee that shape of the mini map, I am having difficulties in clamping the arrows to the shape of the new mini-map. In the rectangular mini map, the arrows stayed in the corners while clamped, but obviously, circles don't have corners.
I am thinking clamping the arrow's x & y positions have to do with the radius of the circle (half of the height of the screen/minimap), but because I'm a little weak on the math side, I am kindly requesting some help. How would I clamp the arrows to the edge of a new circle shape?
The code I have now is as follows:
let {width: canvasWidth, height: canvasHeight} = cc.Canvas.instance.node, // 960, 640
targetScreenPoint = cc.Camera.main.getWorldToScreenPoint(this.targetNode.position)
// other code for rotation of arrow, etc...
// FIXME: clamp the the edge of the minimap mask which is circular
// This is the old clamping code for a rectangle shape.
let arrowPoint = targetScreenPoint;
arrowPoint.x = utils.clamp(arrowPoint.x, (-canvasWidth / 2) + this.arrowMargin,
(canvasWidth / 2) - this.arrowMargin);
arrowPoint.y = utils.clamp(arrowPoint.y, (-canvasHeight / 2) + this.arrowMargin,
(canvasHeight /2) - this.arrowMargin);
this.node.position = cc.v2(arrowPoint.x, arrowPoint.y);
I should probably also note that all mini-map symbols and arrows technically are on screen but only are displayed in on the secondary camera through a culling mask... you know, just in case it helps.
Just for anyone else looking to do the same, I basically normalized the direction from the target node that the arrow points at and multiplied it by the radius of the image mask (with appropriate margin space).
Since the player node and the centre of the mask is at origin, I just got the difference from the player. The (640/2) is the diameter, which of course, shouldn't be hardcoded, but meh for now. Thanks to those who commented and got me thinking in the right direction.
let direction = this.targetNode.position.sub(this.playerNode.position).normalize();
let arrowPos = direction.mul((640/2) - this.arrowMargin);
this.node.position = arrowPos;
I would like to scale animate an SVG element to fit (preserving aspect ratio) a given area of the SVG.
I know about animate which performs relative animations
var s = Snap("#myelement");
s.animate({ 'transform' : 't100,100s5,5,165,175' },1000);
In principle it should be possible to achieve what I want by computing the parameters of the translation and the scaling.
The problem there is that I do not find accurate documentation of the parameters.
The arguments of t seem to be the relative x,y position and that of s the scale factors and the coordinates of the scale center.
However, how does the combined translation and scaling work? Does the relative translation position scale with the scaling, etc.?
In other words: How do I compute the relative translation and scaling parameters from the coordinates of the upper left and the lower right corner of the animation target element?
Alternatively: Is there a more suitable animate function in Snap?
You show a transform with several parts. The order of these parts is important. If you translate first and scale later, the resulting translation is scaled too. If you scale first and then translate the resulting translation is not affected by the scaling.
The animation you use in Snap.svg is the one I also use. (However I consider migrating to svg.js, since Snap.svg does not play well with Electron for example. I have to do some testing first, though)
Since Snap uses SVG syntax, to solve the problem one needs to understand SVG transformations (see here for an introduction: https://sarasoueidan.com/blog/svg-transformations/). In order to set up a combined SVG transformation it is important to understand that each transformation changes the coordinate system (rather than just the properties of an element in an absolute coordinate frame).
If you combine two transformations, scaling and translation, this means that the parameters of the second transformation depends on the first one.
To achieve a translation and scaling of an element to a given location and size in the coordinates of the ViewBox of an SVG, one can first perform the scaling to the new size choosing the center coordinates for the scaling as the center of the element. Then considerations for the following translations simplify as follows
function startAnimation() {
var svg = Snap("#baseSVG");
/* get the bounding box of the svg */
var bboxSvg = svg.getBBox();
var s = Snap("#element");
/* get the bounding box of the element */
var bbox = s.getBBox();
/* get the required scale factor (assuming that we want to fit the element inside the svg bounding box) */
var scale = Math.min(bboxSvg.width/bbox.width,bboxSvg.height/bbox.height)*0.8;
/* compute the translation needed to bring center of element to center of svg
the scale factor must be taken into account since the translation is based on the coordinate system obtained after the previous scaling */
var tx = (200-bbox.cx)/scale;
var ty = (200-bbox.cy)/scale;
/* perform the animation (make center of scaling the center of element) */
s.animate({ 'transform' : 's' + scale + ',' + scale + ',' + bbox.cx + ',' + bbox.cy + 't' + tx + ',' + ty },1000,mina.bounce);
s.drag();
}
This assumes that your SVG object has id baseSVG and the element you want to transform has id element. It is transformed such that it fits the SVG (adjust the factor 0.8 if you want it larger or smaller). If you know only the coordinates of the corners of the element you must first compute the center coordinates of the target (replace bbox.cx and bbox.cy) and the scale to apply this code snippet. This works in the obvious way in the coordinate frame of baseSVG.
Here is the graphics:
http://snag.gy/aVFGA.jpg
the big rectangle is canvas element, the small rectangle is the image object in the canvas. I want to find what is the real distance from the left.
values are such from what I see in console:
regX: 564.256
regY: 41.4
scaleX: 0.4491319444444445
scaleY: 0.4491319444444445
x: 363.3333333333333
y: 409.77777777777777
So as I see x is not real. It somehow relates with regX and scaleX. But I am not finding how it relates. From the image I think the x should be about 100 - 150 px.
THe bigger the x - the more it is to the right.
But the bigger regX - the more it makes rectangle go to the left.
So if I would just take the difference 564.256 - 363.333 = ~200 - left corner of the rectangle should be in them middle of canvas because canvas is 400px widh. But it is not, so substraction does not help. So how do I get how many pixels are in real from the left?
You can do this by using the localToGlobal method (see here).
It depends to which object the given attributes belong.
If they belong to the shape and your rectangle inside the image / shape starts at (0,0):
var point = shape.localToGlobal(0, 0);
// this will calculate the global point of the shape's local point (0,0)
If they belong to the stage:
var point = stage.localToGlobal(yourRectObject.x, yourRectObject.y);
// point.x should contain the position on the canvas
You should use these methods in general because your method might work for the current situation but will probably break as soon as you scale the stage itself or put the shape in a scaled / positioned container.
I guess I found what by experimenting with values:
distanceFromLeft = x - scaleX * regX;
so getting 109.90793888888885 px
If someone has worked more with this library, they could confirm that its not accidental.
I'm trying to use brownian motion to create a group of random moving particles.
http://jsfiddle.net/J75Em/16/
So far I've got the particles moving randomly but I'm not sure how to set the forward direction to make it look more natural.
I've tried to use the change in x and y axis to calculate rotation using atan, you can see this by uncommenting rotate but this doesn't seem to perform well.
Is this the right approach for this type of movement? thanks;
This is pretty neat!
You are sort of going about it the right way but you should actually use the atan2 function. This removes the need for any 0 checks.
The atan2 function gives you an angle which is anticlockwise from the positive x vector
(1, 0) --->
The bees are 90 degrees off from this starting angle so you must subtract 90 degrees from the direction. (depending on which way round you do the dy and dx calculation, you might need to add)
You could find that the direction changes rapidly, so you could consider limiting the next change to a set of changes that cause an angle change below some threshold. This will make the movement a little smoother.
I would actually go about it by generating an angle between say -pi/8 and pi/8 radians, and a random length. Essentially using polar coordinates. Then add this new random polar offset to the x and y position like
newX = currentX + (randomLength * cos(randomAngle + currentAngle)) and
newY = currentY + (randomLength * sin(randomAngle + currentAngle))
If you work with angles you can also get more natural effects like if you want the bees to stay within a certain area, you can force a bias towards the center of the area as they get closer and closer to the edge.
Update:
So I've taken a closer look. The trouble is that you expect .rotate to set the rotation when it actually adds to the rotation
There are 2 options for fixing this.
Rotate by the difference between the previous and the current angle
Set the rotation using the .transform method
You can see solution 2 in action here http://jsfiddle.net/c5A2A/
I'm trying to figure out how I can get the correct "active" tile under the mouse when I have "ramp" and +1 height tiles (see picture below).
When my world is flat, everything works no problem. Once I add a tile with a height of say +1, along with a ramp going back to +0, my screen -> map routine is still looking as if everything is "flat".
In the picture above, the green "ramp" is the real tile I want to render and calculate mouse -> map, however the blue tile you see "below" it is the area which gets calculated. So if you move your mouse into any of the dark green areas, it thinks you're on another tile.
Here is my map render (very simple)
canvas.width = canvas.width; // cheap clear in firefox 3.6, does not work in other browsers
for(i=0;i<map_y;i++){
for(j=0;j<map_x;j++){
var xpos = (i-j)*tile_h + current_x;
var ypos = (i+j)*tile_h/2+ current_y;
context.beginPath();
context.moveTo(xpos, ypos+(tile_h/2));
context.lineTo(xpos+(tile_w/2), ypos);
context.lineTo(xpos+(tile_w), ypos+(tile_h/2));
context.lineTo(xpos+(tile_w/2), ypos+(tile_h));
context.fill();
}
}
And here is my mouse -> map routine:
ymouse=( (2*(ev.pageY-canvas.offsetTop-current_y)-ev.pageX+canvas.offsetLeft+current_x)/2 );
xmouse=( ev.pageX+ymouse-current_x-(tile_w/2)-canvas.offsetLeft );
ymouse=Math.round(ymouse/tile_h);
xmouse=Math.round(xmouse/(tile_w/2));
current_tile=[xmouse,ymouse];
I have a feeling I'll have to start over and implement a world based map system rather than a simple screen -> map routine.
Thanks.
Your assumption is correct. In order to "pick" against world geometry, your routine needs to be aware of the world (and not just the base-level tile configuration). That is, without any concept of the height of the tiles near the one that is currently picked (by your current algorithm), there's no way to determine whether a neighboring tile (or one even further away, depending on the permitted height) should be intercepted by picking ray.
You've got the final possible point of your picking ray, already. What remains is to define the remainder of the ray, in world-space, and to check that ray for intersections with world geometry.
If, like the picture, your view angle is always 45 degrees and always from the same direction, your mouse -> map routine could use an algorithm something like:
calculate i,j of tile as you're doing currently (your final value of xmouse, ymouse)
look up height and angle of tile at i,j
given the height and angle, does this tile intersect the picking ray? If so, set lasti, lastj = i, j
increment/decrement i,j one step diagonally toward viewer
have we fallen off the edge of the map? If so, return lasti, lastj. Otherwise go back to 2.
Depending on the maximum height of a tile, you might have to check only 2 tiles, rather than going all the way to the edge of the map.
3 is the tricky part, and depends on your world geometry. Draw some triangles and you should be able to figure it out. Or you might try looking at the function intersect_quadrilateral_ray() here.