PaperJS - How to move along path and rotate along the path - javascript

The examples shown in here show how to move an object along the path in Paperjs but how do I rotate them correctly along the path?
In the examples shown on the link above, people suggested by using a circle as an example. But once changed to a rectangle new Path.Rectangle(new Point(20,20), new Size(20,20)); you can see that it moves along the path but does not actually rotate in the direction of the path.
How do I calculate the rotation and set it to my object?

In order to calculate the rotation, you need to know the tangent vector to the path at the position of your rectangle.
This can be retrieved with path.getTangentAt(offset) method.
Then, an easy way to animate the rotation of an item is to set item.applyMatrix to false and then animate the item.rotation property on each frame.
Here is a sketch demonstrating the solution.
// Create the rectangle to animate along the path.
// Note that matrix is not applied, this will allow us to easily animate its
// rotation.
var rectangle = new Path.Rectangle({
point: view.center,
size: new Size(100, 200),
strokeColor: 'orange',
applyMatrix: false
});
// Create the path along which the rectangle will be animated.
var path = new Path.Circle({
center: view.center,
radius: 250,
strokeColor: 'blue'
});
// On each frame...
function onFrame(event) {
// ...calculate the time of the animation between 0 and 1...
var slowness = 400;
var time = event.count % slowness / slowness;
// ...and move the rectangle.
updateRectangle(time);
}
function updateRectangle(time) {
// Calculate the offset relatively to the path length.
var offset = time * path.length;
// Get point to position the rectangle.
var point = path.getPointAt(offset);
// Get tangent vector at this point.
var tangent = path.getTangentAt(offset);
// Move rectangle.
rectangle.position = point;
// Rotate rectangle.
rectangle.rotation = tangent.angle;
}

Related

Using D3-Geo to plot a shape

I'm using d3-geo package to plot points and shapes in latitude and longitude space. I would like to plot a simple polygon, triangle, square, star, etc centered on a point location. I would also, if possible, like to plot text at a given [lat,lon].
I currently have the below code working and plotting a ring at the given coords [-40.5, 65.5]. I would, however, like to be able to define different shapes at this location, is there an easy way of doing this without manually defining the shape myself? There is an empty 'properties' field that I'm unable to find any documentation on that could be used? D3-geo documentation and google searches have yielded zilch so far.
let geoGenerator = geoPath()
.projection(projection)
.pointRadius(4)
.context(context); //2d Canvas contect
context.beginPath();
geoGenerator({
type: "Feature",
geometry: {
type: "Point",
coordinates: [-40.5, 65.5]
},
properties: {}
});
context.stroke();
Geojson does not have any property in its specification that specifies the type of symbol (symbol shape, color, size, etc) that should be drawn. Geojson only specifies the geometry (point, line, polygon, etc) of the drawn object in geographic coordinates.
Of course you can use the properties property of a geojson feature to hold symbol data, it just has no effect on the rendering the feature unless you build that functionality yourself.
While geojson doesn't have any specifications for symbology, the geoPath generator in D3 let's you specify one part of a drawn symbol: the radius of a point (as points are dimensionless otherwise). However, other than this, d3-geo doesn't offer any support for drawing specific symbols, it can only project geometry.
To draw a symbol at a specific geographic coordinate, you'll want project the coordinate (projection([longitude,latitude])). Now you have a coordinate in pixel values, you can use that coordinate to draw your symbol. You don't want to try and draw the symbol in geographic coordinates as this isn't scalable and it is dependent on projection.
Here's a simple implementation with d3-symbol (I haven't drawn the rest of the world, just two points, but they are projected properly):
var context = d3.select("canvas").node().getContext("2d");
var points = [[-136,63],[-123,50]];
var projection = d3.geoMercator();
var shape = d3.symbol()
.type(d3.symbolWye)
.context(context)
.size(200);
var shapey = function(lonlat) {
// Get xy Data
var xy = projection(lonlat);
// save without translation.
context.save();
// position symbol:
context.translate(...xy);
// Draw symbol:
context.beginPath();
shape();
context.fill();
// Remove translation:
context.restore();
}
points.forEach(shapey);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<canvas width="500" height="300"></canvas>
Of course you could specify your own shapes too, here's a simple square implementation:
var context = d3.select("canvas").node().getContext("2d");
var points = [[-136,63],[-123,50]];
var projection = d3.geoMercator();
var shape = function(xy) {
var offset = 10;
var x = xy[0];
var y = xy[1];
context.beginPath();
// draw a sqaure:
context.moveTo(x-offset,y-offset);
context.lineTo(x-offset,y+offset);
context.lineTo(x+offset,y+offset);
context.lineTo(x+offset,y-offset);
context.lineTo(x-offset,y-offset);
context.fill();
context.strokeStyle = "steelblue";
context.lineWidth = 5;
context.lineCap = "square"
context.stroke();
}
var shapey = function(lonlat) {
// Get xy Data
var xy = projection(lonlat);
// save without translation.
shape(xy);
}
points.forEach(shapey);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<canvas width="500" height="300"></canvas>
Of course you could get a lot fancier in these basic functions, but for the purposes of demonstration they should be sufficient.

How do I get the center of a circle in paper.js?

I am trying out paper.js and I've made a few circles:
var circle = new Shape.Circle({
center: [100,100],
radius: 50,
strokeColor: 'black',
strokeWidth: 2
});
This draws a circle with center [100,100] (which could also be a point: Point({x:100, y:100});) and radius 50. This is great, this is a circle.
If I want to get the radius of a circle, I can do this:
circle.radius; // returns 50
But I don't know how to get the center back again. Part of the reason I guess is that Shape.Circle returns a Shape object, which does not have a center parameter (documentation here), but surely I can get this point back somehow. Does anyone know how?
Since circles are centred on the position they're created at, you can get the position (as a Point) and the x and y values from that:
var circle = new Shape.Circle({
center: [100,100],
radius: 50,
strokeColor: 'black',
strokeWidth: 2
});
console.log(circle.position.x, circle.position.y); //100 100
From http://paperjs.org/reference/shape/#position
While the circle object does not have a center property, if you read the docs properly you will find that it does have a bounds property.
In graphics, a "bound" is the rectangle that fully contains your object. So for a circle, the bounds will be a rectangle that touches the circle on the left, right, top and bottom. Therefore the center of the bound is the center of the circle (note: this is not always true for all objects depending on your definition of "center").
Paper.js will give you:
circle.bounds.x
circle.bounds.width
circle.bounds.y
circle.bounds.height
Therefore the center of the circle is:
var centerX = circle.bounds.x + circle.bounds.width/2;
var centerY = circle.bounds.y + circle.bounds.height/2;
Note: You will have to try this out yourself as I have zero experience with paper.js. I just read the docs
Use :
circle.center[0] and circle.center[1]
cause its an array
<script>
var circle = {
center: [100,100],
radius: 50,
strokeColor: 'black',
strokeWidth: 2
};
console.log(circle.center[0]);
</script>
Update:
Sorry, I haven't read your question properly.
You can use circle.position to get the position of center.

Three.js setFromRotationMatrix strange behavior when rotation is over 90 degrees

I have objects which each have a separate parent for each rotation axis (1 for X-rotation, 1 for Y-rotation, and 1 for Z-rotation. They are all related to each other in that order as well: X-rotation object is a child of the Y-rotation object. Y-rotation object is a child of the Z-rotation object).
I'm trying to make a feature which allows users to rotate all objects in the scene together (they are all contained in a single Object3D). When that Object3D is rotated, the program must find all of the objects' absolute positions and rotations relative to the world so that the program can output the new values for each object.
To do this, I currently have it setup to move the object so that its position inside the "scene-rotator", which is an Object3D, is set to its absolute position relative to the world. Now, I'm trying to make the rotation of the object become the absolute rotation of the object relative to the world, so that it changes accordingly when the "scene-rotator"'s rotation is changed. Also, the setFromRotationMatrix method was not working correctly when I tried just running it once on the child object, so instead, I had to run it again for each parent object and get each separate rotation from them accordingly
This is the code that I currently have which is supposed to get the absolute rotation of the object relative to the world:
var beforeRotForX = new THREE.Euler();
beforeRotForX.setFromRotationMatrix(objects[i].parent.matrixWorld, "ZYX");
var beforeRotForY = new THREE.Euler(); // Had to be a separate one for some reason...
beforeRotForY.setFromRotationMatrix(objects[i].parent.parent.matrixWorld, "ZYX");
var beforeRotForZ = new THREE.Euler(); // And apparently this one has to be separate too
beforeRotForZ.setFromRotationMatrix(objects[i].parent.parent.parent.matrixWorld, "ZYX");
// Absolute before rotation
objects[i].userData.sceneBeforeRotAbs = {
x: beforeRotForX.x,
y: beforeRotForY.y,
z: beforeRotForZ.z
};
Then, it must apply that absolute rotation to the relative rotation of the object
objects[i].parent.rotation.x = objects[i].userData.sceneBeforeRotAbs.x;
objects[i].parent.parent.rotation.y = objects[i].userData.sceneBeforeRotAbs.y;
objects[i].parent.parent.parent.rotation.z = objects[i].userData.sceneBeforeRotAbs.z;
This all works fine when the Y-rotation of the second parent is within -90 through 90
// Results of absolute world rotation when the Y-rotation of the
// second parent is set to 90 degrees (1.5707... as euler)
objects[i].userData.sceneBeforeRotAbs.x === 0
objects[i].userData.sceneBeforeRotAbs.y === 1.5707963267948966
objects[i].userData.sceneBeforeRotAbs.z === 0
but when the Y-rotation of the second parent is below -90 or greater than 90, then it gives the wrong value for the absolute world X-rotation and Y-rotation as a result
// Results of absolute world rotation when the Y-rotation of the
// second parent is set to 91 degrees (1.5882... as euler)
objects[i].userData.sceneBeforeRotAbs.x === 3.141592653589793
objects[i].userData.sceneBeforeRotAbs.y === 1.5533438924131038
objects[i].userData.sceneBeforeRotAbs.z === 0
You're running into gimbal lock. When using euler angles you'll always run into gimbal lock issues, and you'll encounter unexpected behavior when applying multiple rotations.
For example, in 2D space, a 30° rotation is the same as a -330° rotation. In 3D space, you can get the same problem: rotating an object 180° in the X-axis is the same as giving it a 180° Y-axis + 180° Z-axis rotation.
You should declare your rotations using quaternions, and then multiply them together to get the desired result without gimbal lock issues.
// Declare angles
var angleX = 45;
var angleY = 120;
var angleZ = 78;
// Declare X and Y axes
var axisX = new THREE.Vector3(1, 0, 0);
var axisY = new THREE.Vector3(0, 1, 0);
var axisZ = new THREE.Vector3(0, 0, 1);
// Init quaternions that will rotate along each axis
var quatX = new THREE.Quaternion();
var quatY = new THREE.Quaternion();
var quatZ = new THREE.Quaternion();
// Set quaternions from each axis (in radians)...
quatX.setFromAxisAngle(axisX, THREE.Math.degToRad(angleX));
quatY.setFromAxisAngle(axisY, THREE.Math.degToRad(angleY));
quatZ.setFromAxisAngle(axisZ, THREE.Math.degToRad(angleZ));
// ...then multiply them to get final rotation
quatY.multiply(quatX);
quatZ.multiply(quatY);
// Apply multiplied rotation to your mesh
mesh.quaternion.copy(quatZ);

Adding an animated arrow on an offset Leaflet polyline

I have a project which consist in visualizing the exchange of data between points on a map.
I'm using Leaflet to draw polylines from coordinates in a GeoJson file and Leaflet.polylineDecorator (https://github.com/bbecquet/Leaflet.PolylineDecorator) to put an animated arrow on the polyline.
The thing is that I need to visualize the stream in both directions. I started by adding to my Geojson file polylines in the other direction but the issue is when I zoom out, the two polylines are stacked.
So I found Leaflet.polylineOffset (https://github.com/bbecquet/Leaflet.PolylineOffset) which allows to create an another polyline just by setting the offset option.
I thought, i just had to do the same to put the animated arrow on it but when i'm doing it, the animation is affected to the original polyline. In fact, the offset polyline keeps the coordinates from the original one.
I wanted to know if there is a way to apply this animation to the offset polyline.
Here is my code:
d3.json("data/trajetsFibreDCSigma.json",function (data){ // getting polylines' data from a json file to add them on the map
L.geoJson(data, {
style: function(feature){return {color : feature.properties.stroke,opacity: 1};}, // setting the style of the polylines
onEachFeature: function(feature){
// getting the coordinates of the polyline from the json file
var latlng = feature.geometry.coordinates;
var size = feature.geometry.coordinates.length;
var buffer;
// reversing the order of latitude and longitude in the array because a L.latLng object needs the latitude first and I have the opposite in my json file
for (i=0;i<size;i++)
{
buffer = latlng[i][0];
latlng[i][0] = latlng[i][1];
latlng[i][1] = buffer;
}
var polylineOffset = L.polyline(latlng,{offset: 5,color: 'blue',opacity: 1}).addTo(map); // putting an offset to the polyline
addArrow(latlng,feature);
addArrow(polylineOffset,feature);
}
}).addTo(map);
});
function addArrow(polyline,feature){ // function to add an arrow on the map
var arrowHead = L.polylineDecorator(polyline).addTo(map); // creating an arrow which will be put on the polyline
var arrowOffset = 0;
window.setInterval(function() { // creating an animation for the arrow to cross the polyline
arrowHead.setPatterns([
{offset: arrowOffset+'%', repeat: 0, symbol: L.Symbol.arrowHead({pixelSize: 10, polygon: false,
pathOptions: {stroke: true,color: feature.properties.stroke,opacity: 1}})}
]);
if(++arrowOffset > 100)
arrowOffset = 0;
}, 100);
}
(If I'm just calling addArrow with the offset polyline, it will pop on the original one).
I found a solution to get the offset polyline's coordinates.
The PolylineOffset plugin has a function which returns the offset coordinates.
You can use it like this:
var pts = L.PolylineOffset.offsetLatLngs(latlng,10,map); // getting the coordinates from the offset polyline
where latlng is the array of the original coordinates
; 10 is the offset
; map is your leaflet map

THREE.js lookAt - How to pan smoothly between old and new target positions?

Example JSfiddle
I can get my cone to point at each target sphere in turn (red,green,yellow,blue) using the THREE.js "lookAt" function.
// Initialisation
c_geometry = new THREE.CylinderGeometry(3, 40, 120, 40, 10, false);
c_geometry.applyMatrix( new THREE.Matrix4().makeRotationX( Math.PI / 2 ) );
c_material = new THREE.MeshNormalMaterial()
myCone = new THREE.Mesh(c_geometry, c_material);
scene.add(myCone);
// Application (within the Animation loop)
myCone.lookAt(target.position);
But now I want the cone to pan smoothly and slowly from the old target to the new target. I guess that I can do it by computing intermediate points on the circular arc which is centred at the cone centre Cxyz and which passes from the previous target position Pxyz to the new target position Nxyz.
Please can someone point me to suitable: (a) utilities or (b) trigonometry algorithms or (c) code examples for calculating the xyz coordinates of the intermediate points on such an arc? (I will supply the angular increment between points based on desired sweep rate and time interval between frames).
You want to smoothly transition from one orientation to another.
In your case, you would pre-calculate the target quaternions:
myCone.lookAt( s1.position );
q1 = new THREE.Quaternion().copy( myCone.quaternion );
myCone.lookAt( s2.position );
q2 = new THREE.Quaternion().copy( myCone.quaternion );
Then, in your render loop:
myCone.quaternion.slerpQuaternions( q1, q2, time ); // 0 < time < 1
three.js r.141
For those of you looking to lerp position and lookAt, you can create a initial lookAt target from the current look direction and lerp that towards the final target:
function MoveWhileLookingAt(object: Object3D, destination: Vector3, lookAt: Vector3){
const fromPosition = object.position.clone();
const fromLookAt = new Vector3(
0,
.1, // To avoid initial camera flip on certain starting points (like top down view)
-object.position.distanceTo(lookAt) // THREE.Camera looks down negative Z. Remove the minus if working with a regular object.
);
object.localToWorld(fromLookAt);
const tempTarget = fromLookAt.clone();
function LookAtLerp(alpha: number){
// This goes in your render loop
object.position.lerpVectors(fromPosition, destination, alpha);
tempTarget.lerpVectors(fromLookAt, lookAt, alpha);
object.lookAt(tempTarget);
}
}

Categories