I am trying to use cytoscape.js animations to visualise how nodes are inserted into a specific type of tree. I need to run many different animations including manipulating with position of the nodes in a sequence. When I tried to run them using delay, it didn't work - they didn't go in a sequence, so I am creating animations, pushing them into an array like this(this is an example, I have different kinds of animations):
animationArray.push(cy.nodes("[id='" + node.key + "']").animation({
position: { x: current.x + 50, y: current.y + 50 },
easing : easing,
duration: duration,
complete: function(){}
}));
and then playing them all using a function that goes through the array - this way, they run in a sequence:
playAnimation(animationArray, 0);
function playAnimation(aniArray, i) {
if (i === aniArray.length) {
return;
}
aniArray[i].play().promise().then(function () {
playAnimation(aniArray, i + 1)
})
}
Note: I believe there must be better way to run animations in sequence but this is the best I got.
My problem is that when I change a position of a node in one of the animations, it doesn't stay in its new position and returns back to where it was at the beginning. For example, the node is on position A. In first animation I move it from A to position B and in second animation I want to move it from B to C. But after the first animation, the node is back to A, so it moves from A to C instead. I tried to solve it by setting node's position to B in that complete: function(){} but it didn't work as expected. It looked like all of those complete functions got executed at the same time, not in order with my animation sequence.
Any advice how should I approach this?
I figured it out. I guess that while pushing animation into an array, it is being initialized with current information about the nodes, and animations executed after that doesn't make effect on it. So when pushing second animation, which should move node to C, it looks at node and finds out its position, which is still A, because first animation didn't run yet. So I added something like:
cy.$('#' + x.key).position({
x: current.x + 50,
y: current.y + 50
});
after pushing animation into an array. In the end, I set x position back to the beginning position and then I run animation array.
Related
I am currently working on a project where I need to keep the nodes of a force simulation away from the svg edges.
My attempt was using a exponential curve to increase the centering force for nodes reaching a certain x coordinate.
I am using this function
function getBorderForce(x, width, middle, steepness = 10) {
return Math.pow(((x-middle)/(width/2)), steepness)
}
If the given x value reaches middle + width / 2 it returns a value of 1. The steepness of the increase can modified by steepness parameter. A higher value means the nodes would get repealed later. I hope it is understandable.
I would then implement this function as follows into a d3.forceSimulation():
const sim = d3.forceSimulation().
.force("forceX", d3.forceX().x(d => d.forceX).strength(0.02))
.force("forceY", d3.forceY().y(d => d.forceY).strength(0.1))
.force("edgeRepeal", d3.forceX().x(_ => center).strength(d =>
getBorderForce(d.x, width, center)
))
.nodes(nodes)
When I use this approach my browser crashes and I get an out of memory error.
It works is if I use the d.forceX instead of the d.x value, but that means that nodes closer to the edges would be pushed in and would not be placed on their desired position d.forceX.
Maybe someone has a better idea or can show the flaw in my code – I am relatively new to d3, so it would be no surprise.
All the Best
I think this is ultimately a pretty simple question, but it's hard to describe, thus, I provide a working example here (in the sample press 'z' to see rotation with unwanted translation and 'x' keys to rotate with a compensating re-position).
Basically, I am trying to rotate an object (a thumbstick) about the z-axis of a complex model loaded via gltf (a model of the oculus rift touch controller). It's easy to rotate about the x-axis because it's 90 deg. orthogonal to the x-axis. About the z-axis, it's harder because the plane the thumbstick is attached to is angled at 30 deg. I realize that if the thumbstick were using local coordinates, this wouldn't be a problem, but 'thumb.rotation.z' does not seem to be using local coordinates and is rotating about the model's (as a whole), or maybe even the scene's global y and z (?). Anyway, after a bunch of futzing around, I was able to get things to work by doing the following:
// occulus plane is angle at 30 deg, which corresponds to
// 5 units forward to 3 units down.
var axis = new THREE.Vector3(0, 5, -3).normalize();
factory.thumbstick.geometry.center();
var dir = (evt.key === 'x' ? 1 : -1);
thumb.rotateOnAxis(axis, factory.ONE_DEG * 5.0 * dir);
Basically, I'm rotating about a "tilted" axis, and then calling 'center' to make thumbstick centered on the pivot point, so it rotates about the pivot point, rather than around the pivot point (like the earth orbiting the sun).
Only problem is that when you call 'geometry.center()' and then call 'rotateOnAxis', it translates the thumbstick to the pivot point:
Note: the position on the thumbstick object is (0,0,0) before and after the calls.
I have empirically determined that if I alter the position of the thumbstick after the translation like so:
// magic numbers compensating position
var zDisp = 0.0475;
var yDisp = zDisp / 6.0
thumb.position.x = 0.001;
thumb.position.y = -yDisp;
thumb.position.z = zDisp;
Then it (almost) returns back to it's original position:
Problem is these numbers were just determined by interactively and repeatedly trying to re-position the thumbstick i.e. empirically. I simply cannot find a programmatic, analytical, api kind of way to restore the original position. Note: saving the original position doesn't work, because it's zero before and after the translation. Some of the things I tried were taking the difference between the bounding spheres of the global object and the thumbstick object, trying to come up with some 'sin x- cos x' relation on one distance etc. but nothing works.
My question is, how can I progammatically reverse the offset due to calling 'geometry.center()' and rotateOnAxis (which translates to the pivot point), without having to resort to hacked, empircal "magic" numbers, that could conceivably change if the gltf model changes.
Of course, if someone can also come up with a better way to achieve this rotation, that would be great too.
What's throwing me is the (peceived?) complexity of the gltf model itself. It's confusing because I have a hard time interpreting it and it's various parts: I'm really not sure where the "center" is, and in certain cases, it appears with the 'THREE.AxesHelper' I'm attaching that what it shows as 'y' is actually 'z' and sometimes 'up' is really 'down' etc, and it gets confusing fast.
Any help would be appreciated.
The breakthrough for me on this was to re-frame the problem as how do I change the pivot point for the thumbstick, rather than how do I move the thumbstick to the (default and pre-existing) pivot point. To paraphrase JFK, "ask not how you can move to the pivot, but ask how the pivot can move to you" :-)
After changing my angle of attack, I pretty quickly found the aforementioned link, which yielded my solution.
I posted an updated glitch here, so now pressing z works as I expected. Here is the relevant code portion:
factory.onModelLoaded = function(evt) {
console.log(`onModelLoaded: entered`);
factory.thumbstick = this.scene.children[1].children[2]
let thumb = factory.thumbstick;
// make the thumb red so it's easier to see
thumb.material = (new THREE.MeshBasicMaterial({color: 0xFF7777}));
// use method from https://stackoverflow.com/questions/28848863/threejs-how-to-rotate-around-objects-own-center-instead-of-world-center/28860849#28860849
// to translate the pivot point of the thumbstick to the the thumbstick center
factory.thumbParent = thumb.parent;
let thumbParent = factory.thumbParent;
thumbParent.remove(thumb);
var box = new THREE.Box3().setFromObject( thumb );
box.getCenter( thumb.position ); // this basically yields my prev. "magic numbers"
// thumb.position.multiplyScalar( - 1 );
var pivot = new THREE.Group();
thumbParent.add( pivot );
pivot.add( thumb );
thumb.geometry.center();
// add axeshelp after centering, otherwise the axes help, as a child of thumb,
// will increase the bounding box of thumb, and positioning will be wrong.
axesHelper = new THREE.AxesHelper();
thumb.add(axesHelper);
}
Which allows my "z" handler to just rotate without having to do translation:
case 'z':
case 'Z':
var axis = new THREE.Vector3(0, 5, -3).normalize();
var dir = (evt.key === 'z' ? 1 : -1);
thumb.rotateOnAxis(axis, factory.ONE_DEG * 5.0 * dir);
break;
Interestingly, it's the call to box.getCenter() that generates numbers very close to my "magic numbers":
box.getCenter()
Vector3 {x: 0.001487499801442027, y: -0.007357006114165027, z: 0.04779449797522323}
My empirical guess was {x: 0.001, y: -0.00791666666, z: 0.0475} which is %error {x: 32.7%, y: 7.6%, z: 0.61%}, so I was pretty close esp. on the z component, but still not the "perfect" numbers of box.getCenter().
I'm building a game and I'm currently working on the physics.
I'm using the SAT algorithm to detect collisions. The collisions are between the character (AxisAlignedBoundingBox) and some rectangles (with rotation).
Everything works fine, except the collision near to a corner in specific situations. (This is a pretty known problem but I didn't find any good solutions).
On Example 1, in the second scene the character should move upwards (stay on the obstacle).
It happens to move left.
On Example 2, in the second scene the character should not get up. Sometimes it gets.
I know why this is happening, because of dx and dy, the Minimum Translation Vector isn't always the wanted one.
There are several solutions to this problem, but not a really good one (in terms of solving the problem and not creating others!).
I'm willing to even use a totally different algorithm from the beginning.
Please give me a hint about an algorithm better than the SAT, or some workaround.
THANK YOU!
A picture is worth many words.
The image has two boxes to test the red and the black..
Note how the center of the black box is always on the darker red box when it is just touching.
You can simplify any AABB test by increasing the size of one box by the size of the other. As long as you referance the boxes position by their centers all works well.
// x,y are box centers
var bBox = { w : 100 , h : 50, x : ?, y ? }; // black
var rBox = { w : 200 , h : 200, x : ?, y ? }; // red
to test if bBox is inside rBox
if(bBox.x > rBox.x - (rBox.w + bBox.w)/2 &&
bBox.x < rBox.x + (rBox.w + bBox.w)/2 &&
bBox.y > rBox.y - (rBox.h + bBox.h)/2 &&
bBox.y < rBox.y + (rBox.h + bBox.h)/2)
// boxes are touching.
}
Also works if boxes are moving. You just test if the vector of bBox movement intersects any of rBox's 4 sides.
I have the following code:
var lion = game.add.sprite(2, 2,'lion');
var jump = game.add.tween(lion);
jump.to({x: 1000, y: 1000 }, 10000, Phaser.Easing.Linear.None);
// ...
jump.start();
I create a sprite and would like to make it move between two points, here I am moving the lion from the top left corner to some point at the bottom right (1000,1000). Is it possible to add a bouncing motion to this movement?
At the moment the lion is moving in a straight line, but I would like to make it look as if the lion were jumping, like this:
How would I achieve this? Are tweens actually capable of producing a complex path like this?
Although the API is tricky and pooly documented (imho), I managed to find a good point to achieve this behavior. It took me 2 days to wrap my head around how tweening works and where to apply my changes.
When a tween is created, you can pass it an easing function. The easing I want applys to the Y axis only (the bouncing motion) and the movement to the right applys to the X axis only. Therefore I have to use two individual Tweens:
function vanHalen (v) { // Might as well jump
game.debug.spriteInfo(lion, 32, 32);
return Math.sin(v * Math.PI) * 1;
};
function goLion() {
var move = game.add.tween(lion);
var jump = game.add.tween(lion);
// "Move" is a linear easing function that will move the sprite to (1000,y). It takes the Lion 2 Seconds to get there.
move.to({x: 1000}, 2000);
// The Jump is a function that works on the y axis, uses a customized easing function to "bounce".
jump.to({y: 30}, 500, vanHalen, true, 0, Number.MAX_VALUE, 0);
move.start();
};
The jumping starts automatically and never ends. When the movement to the right is over, the lion will continue bouncing in one place.
The easing function receives a progress value (between 0 and 1), that indicates how far the tween has moved (in percent).
I'm trying to create a visualization with D3 such that nodes are differently sized by a particular attribute and bigger nodes go to the center and smaller nodes go to the outside. I have sizing and clustering and collision detection working, but I can't figure out how to tell the bigger nodes to go to the center.
I've tried messing with the charge, but couldn't convince that to work. I got linkDistance to move the bigger ones to the center, but (a) getting there was VERY jittery and (b) the smaller ones are way outside rather than tightly packed. The linkDistance is still in the code, just commented out.
It's up at http://pokedex.mrh.is/stats/index.html:
The relevant code (I assume) is also below. The nodes are sized per their attr attribute. Oh, and the nodes are Pokémon.
force = d3.layout.force()
// .gravity(0.05)
// .charge(function(d, i) { return d.attr; })
// .linkDistance(function(d) {
// return 50000/Math.pow(d.source.attr+d.target.attr,1);
// })
.nodes(pokemon)
// .links(links)
.size([$(window).width(), $(window).height()]);
The following gave me a less jittery version of what you have now.
force = d3.layout.force()
.gravity(0.1)
.charge(function(d, i) { return -d[selectedAttr]})
.friction(0.9)
.nodes(pokemon)
.size([$(window).width(), $(window).height()]);
To answer your actual question, each node's coordinates are currently being placed in your graph at random. I quote from the D3 documentation:
When nodes are added to the force layout, if they do not have x and y attributes already set, then these attributes are initialized using a uniform random distribution in the range [0, x] and [0, y], respectively.
From my experience, there's no magic force method that gets the nodes you want to the center of the map. The way that I've accomplished your desired result in the past has been by replacing the randomized coordinates of each node with coordinates that place the nodes in a the desired order, expanding from the center of the map.