How can I apply an SVGMatrix to an array of points? - javascript

Are there built-in libraries to multiply a vector of points by an SVGMatrix?
I have an SVG drawing that has been scaled, and I want to annotate that drawing in its original coordinate system with a line that has a fixed width in screen space. (I.e. the line should not change width when zooming in or out, but lines in the image do, of course.) So, my approach is to transform the image inside a , and then take my array of points and apply the same transformation, then create a new path object at the root level using these transformed points.
I'm looking for the cleanest way to do this.

The svg element has methods from creating matrix objects and point objects. The matrix object has methods for matrix operations (e.g. multiply, translate, scale, etc). The point object has method to apply matrix transform.
For example...
var svg = document.getElementById("mySvg");
var matrix1 = svg.createSVGMatrix();
var matrix2 = matrix1.translate(2, 3);
var point1 = svg.createSVGPoint();
point1.x = 1;
point1.y = 1;
var point2 = point1.matrixTransform(matrix2);
Documentation for the matrix and point objects can be found at...
http://www.w3.org/TR/SVG/single-page.html#coords-InterfaceSVGPoint
http://www.w3.org/TR/SVG/single-page.html#coords-InterfaceSVGMatrix

Related

Paper.js closed arc changes its dimensions on rotation

I've drawn a simple half circle shape using a closed arc, the arc is created using a start point (x,y), end point (x,y) and a through point (x,y)
var from = new Point(20, 20);
var through = new Point(60, 20);
var to = new Point(80, 80);
var path = new Path.Arc(from, through, to);
path.closed = true;
its width and length are NOT the same, when i rotate it at any degree, its dimensions start morphing.. why is that happening and how can i fix it?
note that this only happens when i set the length and width of the shape to be different using the path.size property.
path.size = [calcWidth, calcHeight];
the problem is as you see in the images below.
What are you trying to do when setting the path.size property (which is not documented http://paperjs.org/reference/path/) ?
I guess that you should instead use the path.scale() function.
solved..i was using both size property and scale function on my shape. what was causing the problem is i would rotate the shape and THEN call the scale function.
instead i set the size, then scale the shape and after that call the rotate function. this will allow the shape to maintain its dimensions without any changes

Add Points to a Point Cloud with User Mouse Clicks

I'm using Three.js to render point cloud data retrieved from a server.
For each data set, I loop over the data points and create a Three.js Vector3 object with x, y & z values corresponding to each data point. I push each of these vertices onto a list which I then pass into the vertices prop of my geometry component within my points component.
render() {
this.pointCloudVertices = [];
if (this.props.points) {
const points = this.props.points
for (let i = 0; i < points.x.length; i++) {
const vertex = new THREE.Vector3();
vertex.x = points.x[i]
vertex.y = points.y[i]
vertex.z = points.z[i]
this.pointCloudVertices.push(vertex);
}
}
return (<points>
<geometry vertices={this.pointCloudVertices}/>
<pointsMaterial
color={ (Math.floor(Math.random()*16777215)) }
size={ 0.2 }
/>
</points>);
}
https://github.com/caseysiebel/pc-client/blob/master/src/components/PointCloud.js
I'd like the user to be able to use their mouse to add points to another point cloud (points component) by clicking inside the canvas.
I found a lot of resources pointing to the Three.js' Raycaster, but this tool seems to be more for selecting out objects already in the canvas. In my case I'd like the user to be able to click on and area in the canvas not occupied by an object, have the client work out the x, y & z coordinates of that click and then add a vertex, with those x/y/z values, to a points component (likely empty until the user adds points via this modality).
I'm a little confused as to how I will convert 2D mouse events into a 3D vertex value. If anyone knows any good resources on this subject I'd love to check them out.
With THREE.Raycaster(), I see several solutions:
1. Use the .at() method of the .ray property. Like this:
raycaster.ray.at(100 + Math.random() * 150, rndPoint);
Here you can set the constraints for the distance from the origin of the ray, and it will look like this from your original camera:
and how it will look like from aside:
jsfiddle example. You can switch the lines off there.
2. Use the .intersectObjects() method. Where intersecting objects are planes of constraints. For example, we have planes in the form of a cube. When we cast a ray through them, we always intersect two planes, and the array of intersectec objects will be sorted by distance from the origin of the ray. Thus the first element in this array will be the closest plane. So, when we know it, we can take their points of intersection and sub point1 from point2, getting a new vector (its length and direction). And then we'll just set a point at a random place along the vector from point1 to point2:
intersected = raycaster.intersectObjects(planes.children);
if (intersected.length > 0){
var point1 = intersected[0].point;
var point2 = intersected[1].point;
var diff = point2.clone().sub(point1);
var diffLength = diff.length();
rndPoint = point1.clone().addScaledVector(diff.normalize(), Math.random() * diffLength);
. . .
}
It will look like this from the front camera:
and from aside:
jsfiddle example. Lines are switchable here too.
Or you can use THREE.Raycaster() with THREE.OrthographicCamera(). Which is simplier )

Snap.svg: What does Matrix.x(x,y) provide?

Below is an example of using Snap.svg's Matrix.x(x,y) and I'm trying to determine how it is used.
After creating the Matrix transforms below(see image), and requesting the Matrix.x(50,50), it returns a value of 315.47+. What is that value?
Thanks
var SNPsvg = Snap("#mySVG");
//---square, center(0,0)---
var rect = SNPsvg.rect(-30,-30,60,60).attr({fill: 'blue' });
var myMatrix = Snap.matrix();
myMatrix.translate(200,100)
myMatrix.scale(2,1.5)
myMatrix.skew(30,45)
myMatrix.rotate(30)
rect.transform(myMatrix)
var mX1=myMatrix.x(50,50)//--if add translate (50,50) ??---
Its the value of x when transformed by that matrix!
Typically you will use it alongside y,
var TransformedPt = {
x: Matrix.x(x,y),
y: Matrix.y(x,y),
}
However, I would first look into Snaps transform strings, as they are often easier. For example..
rect.transform('t200,200r30s2,3')
Would transform the rect, translating 200,200, rotating (around center) 30 degrees, and then scaling x,y 2,3
It's there to help avoid the need for dealing with matrices. See here also.
The documentation states that Matrix.x(x, y):
Returns x coordinate for given point after transformation described by the matrix.
From http://snapsvg.io/docs/#Matrix.x

Animate a circle being drawn using Paper.js

I'm trying to animate a circle being drawn using Paper.js.
As the circle tool is just a quick access for instantiating a path constructed via moveTo/arcTo etc, there are no arguments to support start and end angles (for open pie chart-like circles).
What I am looking for is a way to animate the circle being drawn from it's first point to an angle of my choice at a certain radius.
The actual canvas specification allows for explicit startAngle and endAngle to be specified. If this was the case within Paper.js I could easily achieve what I am looking for. However, within Paper.js I have yet to come across a method of replicating such control. I created something in Fabric.js that worked as Fabric's implementation of the circle shape used the same attributes as the arc command in the specification.
Does anyone know of a way this can be achieved so I can animate the endAngle?
Here's a conversion function that accepts html5 canvas arc arguments and returns the from, through, to arguments needed for a Paper.js arc.
function canvasArcToPaperArc(cx,cy,radius,startAngle,endAngle,strokecolor){
var startX=cx+radius*Math.cos(startAngle);
var startY=cy+radius*Math.sin(startAngle);
var endX=cx+radius*Math.cos(endAngle);
var endY=cy+radius*Math.sin(endAngle);
var thruX=cx+radius*Math.cos((endAngle-startAngle)/2);
var thruY=cy+radius*Math.sin((endAngle-startAngle)/2);
var from = new Point(startX,startY);
var through = new Point(thruX,thruY);
var to = new Point(endX,endY);
return({from:from, through:through, to:to});
}

Dynamic simplification with projection in D3

I am drawing an SVG map with D3 using the d3.geo.mercator() projection.
I also use a zoom behaviour with the map which applies a transform to the <g> object holding all paths of the map.
After looking at examples of dynamic simplification by Mike Bostock (http://bl.ocks.org/mbostock/6252418) I wonder whether I can apply such an algorithm in my case to redraw the geometry with fewer points when it's zoomed out?
In all examples I've seen, there is a simplify function which skips negligible points and plots the rest as it is, and that function is used in var path = d3.geo.path().projection(simplify). I can't use it like that since I need it to be applied on top of already existing projection = d3.geo.mercator().scale(*).translate([*,*]).
How should I use dynamic simplification with existing projection?
According to the example you quoted, Dynamic Simplification II
The simplify function would be something like
var simplify = d3.geo.transform({
point: function(x, y, z) {
if (z >= area) {
this.stream.point(x, y);
}
}
});
Where area is a treshold variable that you can set beforehand or modify dinamically according to zoom.
Then you would use it on the projection method of d3.geo.path() like
var path = d3.geo.path()
.projection(simplify);
That's more or less the situation you described in your answer. Now, according to Dynamic Simplification IV, the projection method could also be defined as
var path = d3.geo.path()
.projection({
stream: function(s) {
return simplify.stream(s);
}
});
This is exactly the same as before. It's just "expanding" the default methods. d3.geo.path always calls the projection stream method, so you can declare your own stream and forward it to simplify.stream.
Now, you say you need to reproject your path using d3.geo.mercator().
var mercatorProjection = d3.geo.mercator().scale(*).translate([*,*]);
No problem: the streams are chainable. You can do:
var path = d3.geo.path()
.projection({
stream: function(s) {
return simplify.stream(mercatorProjection.stream(s));
}
});
As well as:
var path = d3.geo.path()
.projection({
stream: function(s) {
return mercatorProjection.stream(simplify.stream(s));
}
});
The only difference being that the treshold area would have to be calculated differently if you're dealing with WGS84, pixels or another coordinate system.
Important Caveat, the z parameter in the simplify function isn't the altitude. It is the area of the triangle defined by each point, a precalculated value that's part of TopoJSON sweetness.
I'm afraid this means you can't rely on this example to simplify regular geoJSON. You'll have to add your own logic to calculate each point's related area (if you want to apply Visvalingam's algorithm) or the distance to the closest point (if you want to apply Douglas-Peucker algorithm) or implement your own algorithm.
Good luck.

Categories

Resources