SnapSVG rotation issue after first rotation - svg

I have created a small demo in this codepen.
When clicking any of the four buttons, the object is rotated to the correct position using the following...
function rotateTo(angle) {
var ring = paper.select("#INNER_RING");
var bbox = ring.getBBox();
var cx = bbox.cx;
var cy = bbox.cy;
var trans = 'r' + angle + ' ' + cx + ' ' + cy; // + 's1,1,' + cx + ',' + cy;
console.log(trans);
ring.stop().animate({
transform: trans
},
750,
mina.linear,
function() {
settings.currentRotation = angle;
});
}
On the first rotation, everything goes great. However, subsequent rotations not only rotate but appear to scale as the rotation takes place. The object does end up in the correct final position, but the scaling is not wanted.
What could be happening as part of the first transformation that would cause subsequent ones to do this?

Animating matrices from a previous matrix typically gets pretty messy, as they often don't do things that we think they do (I asked a previous similar question in the past here which may be of use, getting your head around matrix animation if you wanted a deeper understanding). At the start of the first animation, there is no transform matrix to animate from, so it's all pretty straightforward.
However, at the end of the 1st animation, there is a transform matrix left there applying to the element, which causes a problem, as the next animation it tries to animate from that last matrix.
It isn't quite clear what you actually want to do, on a subsequent button press. Whether you want to rotate 'another' 90 degrees, or just rotate 90 from scratch again.
If you just want to repeat the animation, you could just reset the transform to scratch, which simply gets around the problem (but note, there may be an issue if you press the button mid animate).
ring.attr('transform','')
jsbin
If you want to do more complex things, and need to animate from a previous state, I would use a slightly different method of animate, and not animate on an element, but use Snap.animate and build your transform string each time. So the could could look more like this...
var rotateFrom = settings.currentRotation;
var rotateTo = rotateFrom + parseInt(angle)
Snap.animate(rotateFrom, rotateTo, function( val ) {
ring.transform( 'r' + val + ',' + cx + ',' + cy )
}, 750, mina.linear, function() {
settings.currentRotation = rotateTo;
});
jsbin2
Snap.animate() takes 2 initial arguments that are a starting value and an end value, and then interpolates between them, passing that as 'val' to the function which is passed in. The docs for that can be found here
This way, you don't care about animating from previous transforms (which are stored as matrices), and you control the real specifics of whats happening each animation loop.
You may just need to tweak it and store the rotation each time the func is called (rather than just when it finishes), depending on what you want to do mid-animation if the button is pressed before it's finished.

Related

(2D) Trying to make the player smoothly rotate to the mouse position but it rotates backwards when it becomes a negative number

I am trying to rotate my top down player to the mouse position, I got it working but when it reaches the edge of the rotation degress it goes from a positive number into a negative number causing the rotation to go backwards instead of continuing the rotation, now I know why it happens but I cant figure out how to fix it. (reason to not use look_at is because I cant make it smooth to the mouse position.)
func apply_rotation(_delta):
var mouse_direction = position - get_global_mouse_position() # Get the direction
var correction_angle = 90 # make it so the player is facing correctly to the mouse
var angle = rad2deg(-atan2(mouse_direction.x, mouse_direction.y)) - correction_angle # Angle to the mouse
$Rotation.rotation_degrees = lerp($Rotation.rotation_degrees,angle,5 * _delta) # Lerp the angle smoothly
$CollisionShape2D.rotation_degrees = $Rotation.rotation_degrees
I tried your code, and it works fine.
I think the problem is in the first line of the function, you should changeposition, to $Rotation.position.
It should work fine then!

Edit SurfaceTool vertices based on coordinates

Godot Version: v4.0-alpha15
I have a terrain being generated using the SurfaceTool and the MeshInstance3D. I am also moving a purple decal acrossed the surface based on the 3D mouse position. Below is a screenshot of what this looks like.
I want to take the 3D mouse position and raise/lower the terrain on an action press. I found the MeshDataTool but am not quite sure if this allows for that and also not completely sure how to convert the 3D mouse position to the corresponding vertices.
At this point I am sort of completely stuck as there's not a whole lot of documentation that I could find that helps.
I appreciate the help in advance!
I was actually able to figure this out using the MeshDataTool as I mentioned in the original post.
I use the Vector3::distance_to method to get any vertex within 3 of the target position, then I use MeshDataTool::set_vertex in combination with Vector3 methods to add and subtract each of those vertex positions.
var target_position = Vector3(0, 0, 0)
var mdt = MeshDataTool.new()
var mesh = terrain_mesh_instance.mesh
mdt.create_from_surface(mesh, 0)
var points = get_vertex_id(mdt, target_position)
for i in points:
if Input.is_action_pressed("shift"):
mdt.set_vertex(i, mdt.get_vertex(i) + Vector3(0, 1 * delta,0))
elif Input.is_key_pressed(KEY_ALT):
var coords = mdt.get_vertex(i)
coords.y = 0
mdt.set_vertex(i, coords)
else:
mdt.set_vertex(i, mdt.get_vertex(i) + Vector3(0, -1 * delta,0))
# This fixes the normals so the shadows work properly
for face in mdt.get_face_count():
var vertex = mdt.get_face_vertex(face, 0)
var normal = mdt.get_face_normal(face)
mdt.set_vertex_normal(vertex, normal)
mesh.clear_surfaces()
mdt.commit_to_surface(mesh)

Moving an object in the opposite direction to the cursor

I am trying to accomplish a simple task using a 2D graphics library called paperscript. I have a grid of dots and I would like to recalculate the position of those dots based on the position of the mouse cursor so that the dot is displaced from it's original position in the opposite direction that the mouse cursor is from the original position, and displaced by a distance that is inversely proportional to the distance of the mouse cursor to the original position. Hopefully this diagram makes it a little clearer:
I know how to get the current position of the mouse, as well as the position of each dot. What I have been having trouble with, is creating a function that will take those two variables and use it to calculate a new position for each dot that will create the above described effect.
I have a jsfiddle here with what I've created so far.
https://jsfiddle.net/yc62k/247xwh8q/4/
function onFrame(event) {
//Loop through all the dots
for (i = 0; i < count; i++) {
var item = project.activeLayer.children[i];
//Update the position of the dot based on the mouse position
??????
item.position = new Point(
(newPosition.x),
(newPosition.y)
);
}
}
Can anyone suggest an algorithm I can use in this function to get this effect? Or point me in the direction of the maths I would use to solve this problem? Any help would be greatly appreciated!
If the old position of the dot is pDot and the position of the mouse is pMouse, then the direction of movement is
dir = pDot - pMouse
To establish the desired scale (inversely proportional to the distance), just divide by the squared length. Then, the new position is:
pDotNew = pDot + dir * (1.0 / squaredLength(dir))
Be careful about how often you update the positions. If you update them very frequently, the points might move very fast. If this is the case, multiply the direction with a small number (between 0 and 1). Ideally, this number should depend on the update interval in order to maintain a consistent movement speed.

transition a circle into a line by unrolling it with SVG and d3

For a project we are trying to make a circle into a line (and back again) while it is rotating along a linear path, much like a tire rotates and translates when rolling on a road, or a curled fore finger is extended and recurled into the palm.
In this Fiddle, I have a static SVG (the top circle) that rotates along the linear black path (which is above the circle, to mimic a finger extending) that is defined in the HTML.
I also use d3 to generate a "circle" that is made up of connected points (and can unfurl if you click on/in the circle thanks to #ChrisJamesC here ), and is translated and rotated
in the function moveAlongLine when you click on the purple Line:
function moveAlongLine() {
circle.data([lineData])
.attr("transform", "translate(78.5,0) rotate(-90, 257.08 70) ")
.duration(1000)
circle.on("click", transitionToCircle)
}
The first problem is that the .duration(1000) is not recognized and throws a Uncaught TypeError: Object [object Array] has no method 'duration' in the console, so there is a difference between the static definition of dur in SVG and dynamically setting it in JS/D3, but this is minor.
The other is should the transform attributes be abstracted from one another like in the static circle? in the static circle, the translate is one animation, and the rotation is another, they just have the same star and duration, so they animate together. How would you apply both in d3?
The challenge that I can not get, is how to let it unroll upwards(and also re-roll back), with the static point being the top center of the circle also being the same as the leftmost point on the line.
these seem better:
I should try to get the unfurl animation to occur while also rotating? This seems like it would need to be stepwise/sequential based...
Or Consider an octogon (defined as a path), and if it were to rotate 7 of the sides, then 6, then 5.... Do this for a rather large number of points on a polyhedron? (the circle only needs to be around 50 or so pixels, so 100 points would be more than enough) This is the middle example in the fiddle. Maybe doing this programmatically?
Or This makes me think of a different way: (in the case of the octogon), I could have 8 line paths (with no Z, just an additional closing point), and transition between them? Like this fiddle
Or anything todo with keyframes? I have made an animation in Synfig, but am unsure ho get it to SVG. The synfig file is at http://specialorange.org/filedrop/unroll.sifz if you can convert to SVG, but the xsl file here doesn't correctly convert it for me using xsltproc.
this seems really complicated but potential:
Define a path (likely a bézier curve with the same number of reference points) that the points follow, and have the reference points dynamically translate as well... see this for an concept example
this seems complicated and clunky:
Make a real circle roll by, with a growing mask in front of it, all while a line grows in length
A couple of notes:
The number of points in the d3 circle can be adjusted in the JS, it is currently set low so that you can see a bit of a point in the rendering to verify the rotation has occurred (much like the gradient is in the top circle).
this is to help students learn what is conserved between a number line and a circle, specifically to help learn fractions. For concept application, take a look at compthink.cs.vt.edu:3000 to see our prototype, and this will help with switching representations, to help you get a better idea...
I ended up using the same function that generates the circle as in the question, and did a bit of thinking, and it seemed like I wanted an animation that looked like a finger unrolling like this fiddle. This lead me to the math and idea needed to make it happen in this fiddle.
The answer is an array of arrays, with each nested array being a line in the different state, and then animate by interpolating between the points.
var circleStates = [];
for (i=0; i<totalPoints; i++){
//circle portion
var circleState = $.map(Array(numberOfPoints), function (d, j) {
var x = marginleft + radius + lineDivision*i + radius * Math.sin(2 * j * Math.PI / (numberOfPoints - 1));
var y = margintop + radius - radius * Math.cos(2 * j * Math.PI / (numberOfPoints - 1));
return { x: x, y: y};
})
circleState.splice(numberOfPoints-i);
//line portion
var lineState = $.map(Array(numberOfPoints), function (d, j) {
var x = marginleft + radius + lineDivision*j;
var y = margintop;
return { x: x, y: y};
})
lineState.splice(i);
//together
var individualState = lineState.concat(circleState);
circleStates.push(individualState);
}
and the animation(s)
function all() {
for(i=0; i<numberOfPoints; i++){
circle.data([circleStates[i]])
.transition()
.delay(dur*i)
.duration(dur)
.ease("linear")
.attr('d', pathFunction)
}
}
function reverse() {
for(i=0; i<numberOfPoints; i++){
circle.data([circleStates[numberOfPoints-1-i]])
.transition()
.delay(dur*i)
.duration(dur)
.ease("linear")
.attr('d', pathFunction)
}
}
(Note: This should be in comments but not enough spacing)
Circle Animation
Try the radial wipe from SO. Need to tweak it so angle starts at 180 and ends back at same place (line#4-6,19) and move along the X-axis (line#11) on each interation. Change the <path... attribute to suit your taste.
Line Animation Grow a line from single point to the length (perimeter) of the circle.
Sync both animation so that it appears good on all browsers (major headache!).

Raphael 2 rotate and translate

Here is my script:
<script>
Raphael.fn.polyline = function(pointString) {
return this.path("M" + pointString);
};
window.onload = function() {
var paper = Raphael("holder", 500, 500);
paper.circle(100, 175, 70).attr({"stroke-width":10, "stroke":"red"});
var a = paper.polyline("92,102 96,91 104,91 108,102").attr({"fill":"green", "stroke-opacity":"0"}).rotate(25, 100, 175);
var b = paper.polyline("92,102 96,91 104,91 108,102").attr({"fill":"green", "stroke-opacity":"0"}).rotate(45, 100, 175);
var c = paper.polyline("92,102 96,91 104,91 108,102").attr({"fill":"green", "stroke-opacity":"0"}).rotate(65, 100, 175);
var group = paper.set();
group.push(a, b, c);
group.translate(60);
};
</script>
When I use raphael-1.5.2, the result is:
When I use raphael 2.0, the result is:
In 1.5.2 it uses the rotate transformation to rotate the objects around the circle and in 2.0 it uses the matrix transformation. I assume the matrix transformation transforms the coordinate system for that object, so when you later translate the object in the xy direction it translates it in the xy that is relative for that object.
I need to be able to add green objects around the edge of the red circle and then be able to drag and move everything in the same direction. Am I stuck using 1.5.2 or am I just missing how translate has changed in 2.0?
Use an absolute transform instead of translate. Say you want to move of 100 in x and 50 in y do this:
Element.transform("...T100,50");
Make sure you use a capital T and you'll get an absolute translation. Here's what the documentation says about it:
There are also alternative “absolute” translation, rotation and scale: T, R and S. They will not take previous transformation into account. For example, ...T100,0 will always move element 100 px horisontally, while ...t100,0 could move it vertically if there is r90 before. Just compare results of r90t100,0 and r90T100,0.
See documentation
Regarding translate, according to the documentation in Raphael JS 2.0 translate does this:
Adds translation by given amount to the list of transformations of the element.
See documentation
So what happens is it appends a relative transformation based on what was already applied to the object (it basically does "...t100,50").
I suspect that with 1 your transform correctly treats the set as one object but with 2 the little greeny things rotate indepently
Two is a complete redesign so little disconnects like this will occur
Use getBBox and find the centre of your set, then use 1 rotate command on the whole set specifying cx cy derived from getBBox

Resources