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

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

Related

3D model in HTML/CSS; Calculate Euler rotation of triangle

TLDR; Given a set of triangle vertices and a normal vector (all in unit space), how do I calculate X, Y, Z Euler rotation angles of the triangle in world space?
I am attemping to display a 3D model in HTML - with actual HTML tags and CSS transforms. I've already loaded an OBJ file into a Javascript class instance.
The model is triangulated. My first aim is just to display the triangles as planes (HTML elements are rectangular) - I'll be 'cutting out' the triangle shapes with CSS clip-path later on.
I am really struggling to understand and get the triangles of the model rotated correctly.
I thought a rotation matrix could help me out, but my only experience with those is where I already have the rotation vector and I need to convert and send that to WebGL. This time there is no WebGL (or tutorials) to make things easier.
The following excerpt shows the face creation/'rendering' of faces. I'm using the face normal as the rotation but I know this is wrong.
for (const face of _obj.faces) {
const vertices = face.vertices.map(_index => _obj.vertices[_index]);
const center = [
(vertices[0][0] + vertices[1][0] + vertices[2][0]) / 3,
(vertices[0][1] + vertices[1][1] + vertices[2][1]) / 3,
(vertices[0][2] + vertices[1][2] + vertices[2][2]) / 3
];
// Each vertex has a normal but I am just picking the first vertex' normal
// to use as the 'face normal'.
const normals = face.normals.map(_index => _obj.normals[_index]);
const normal = normals[0];
// HTML element creation code goes here; reference is 'element'.
// Set face position (unit space)
element.style.setProperty('--posX', center[0]);
element.style.setProperty('--posY', center[1]);
element.style.setProperty('--posZ', center[2]);
// Set face rotation, converting to degrees also.
const rotation = [
normal[0] * toDeg,
normal[1] * toDeg,
normal[2] * toDeg,
];
element.style.setProperty('--rotX', rotation[0]);
element.style.setProperty('--rotY', rotation[1]);
element.style.setProperty('--rotZ', rotation[2]);
}
The CSS first translates the face on X,Y,Z, then rotates it on X,Y,Z in that order.
I think I need to 'decompose' my triangles' rotation into separate axis rotations - i.e rotate on X, then on Y, then on Z to get the correct rotation as per the model face.
I realise that the normal vector gives me an orientation but not a rotation around itself - I need to calculate that. I think I have to determine a vector along one triangle side and cross it with the normal, but this is something I am not clear on.
I have spent hours looking at similar questions on SO but I'm not smart enough to understand or make them work for me.
Is it possible to describe what steps to take without Latex equations? I'm good with pseudo code but my Math skills are severely lacking.
The full code is here: https://whoshotdk.co.uk/cssfps/ (view HTML source)
The mesh building function is at line 422.
The OBJ file is here: https://whoshotdk.co.uk/cssfps/data/model/test.obj
The Blender file is here: https://whoshotdk.co.uk/cssfps/data/model/test.blend
The mesh is just a single plane at an angle, displayed in my example (wrongly) in pink.
The world is setup so that -X is left, -Y is up, -Z is into the screen.
Thank You!
If you have a plane and want to rotate it to be in the same direction as some normal, you need to figure out the angles between that plane's normal vector and the normal vector you want. The Euler angles between two 3D vectors can be complicated, but in this case the initial plane normal should always be the same, so I'll assume the plane normal starts pointing towards positive X to make the maths simpler.
You also probably want to rotate before you translate, so that everything is easier since you'll be rotating around the origin of the coordinate system.
By taking the general 3D rotation matrix (all three 3D rotation matrices multiplied together, you can find it on the Wikipedia page) and applying it to the vector (1,0,0) you can then get the equations for the three angles a, b, and c needed to rotate that initial vector to the vector (x,y,z). This results in:
x = cos(a)*cos(b)
y = sin(a)*cos(b)
z = -sin(b)
Then rearranging these equations to find a, b and c, which will be the three angles you need (the three values of the rotation array, respectively):
a = atan(y/x)
b = asin(-z)
c = 0
So in your code this would look like:
const rotation = [
Math.atan2(normal[1], normal[0]) * toDeg,
Math.asin(-normal[2]) * toDeg,
0
];
It may be that you need to use a different rotation matrix (if the order of the rotations is not what you expected) or a different starting vector (although you can just use this method and then do an extra 90 degree rotation if each plane actually starts in the positive Y direction, for example).

three.js lookAt() : how to point some local axis which *isn't* the positive Z axis towards another object

I'm creating an app where a person (right now I'm using a cone-shape) is standing on some surface (right now I'm using a cylinder laid lengthwise) and I'd like their feet to orient toward some point (right now it's the center of the cylinder).
(edit: I just realized that my Z axis in this photo is pointing in the wrong direction; it should be pointing towards the camera, but the question remains unchanged.)
Here is a version of the code similar to what I'm trying to accomplish. https://codepen.io/liamcorbett/pen/YMWayJ (Use arrow keys to move the cone)
//...
person = CreatePerson();
person.mesh.up = new THREE.Vector3(0, 0, 1);
//
// ...
//
function updateObj(obj, aboutObj=false){
let mesh = obj.mesh;
if (aboutObj) {
mesh.lookAt(
aboutObj.mesh.position.x,
aboutObj.mesh.position.y,
mesh.position.z)
};
}
//
// ...
//
function animate() {
// ...
updateObj(person);
// ...
}
The code above gives me something similar to what I'm looking for, but the issue is that lookAt() seems to always point the local Positive Z-axis in some direction, and I'd much prefer that it point the local Negative Y-axis instead.
I'd prefer to not change the x,y,z axes of the model itself, as I feel that's going to be a pain to deal with when I'm applying other logic to the person object.
Is there a way to change which axis lookAt() uses? Or am I going to have to roll my own lookAt() function? Thanks ~
Is there a way to change which axis lookAt() uses?
No, the default local forward vector for 3D objects (excluding cameras) is (0, 0, 1). Unlike other engines, three.js does not allow to configure the forward vector, only the up vector. But this is not really helpful in your case.
You can try to transform the geometry in order to achieve a similar effect.
If you don't want to do this for some reasons and you still want to use Object3D.lookAt(), you have to compute a different target vector (so not the cylinder's center).
Even if the forward vector of the lookAt method can't be changed (as #Mugen87 said), you can still adjust the local rotation afterwards by knowing in advance the difference between the forward Z axis used, and the axis you consider your mesh to be "upward" (ex: a person standing up on the Y axis).
Basically, in your case, just add this line after the lookAt method :
mesh.rotateOnAxis( new THREE.Vector3(1,0,0), Math.PI * -0.5 );
And the cone will look up :)

Need to scale already projected data using d3 geoPath.projection(null)

Based on d3 (ver 1.4) documentation https://github.com/d3/d3-geo/blob/master/README.md#geoProjection , the d3 projection should be set to null to use the data's raw coordinates. How do I scale if the projection looks correct using null? Here is the code:
var path = d3.geoPath()
.projection(null)
.context(context);
bands.features.forEach(function(d, i) {
context.beginPath();
context.fillStyle = colors[i];
context.globalAlpha = .5;
path(d);
context.fill();
});
I have tried defining my own projection but the projection looks incorrect. Here is the code
var project = d3.geoProjection(function(x,y){
return [x,y]
});
var path = d3.geoPath()
.projection(project)
.context(context);
I would take a look at d3.geoTransform which should be better suited for showing already projected Cartesian data than d3.projection. From Mike Bostock:
But what if your geometry is already planar? That is, what if you just want to take projected geometry, but still translate or scale it to fit the viewport?
You can implement a custom geometry transform to gain complete control over the projection process.
To see a better example than what I can do here (and to read the rest of the quote), see this Bl.ock
For example, for your case you might use something like:
function scale (scaleFactor) {
return d3.geoTransform({
point: function(x, y) {
this.stream.point(x * scaleFactor, y * scaleFactor);
}
});
}
path = d3.geoPath().projection(scale(2));
As for why the custom projection shown
var project = d3.geoProjection(function(x,y){
return [x,y] });
changes/distorts the projection, I do not know (I had similar results testing this answer), but if this projection's output is useable, it can be scaled fairly easily:
var project = d3.geoProjection(function(x,y){
return [x,y] }).scale(1000);
Thanks for your suggestion. My problem turned out to not be a scale problem but a matrix transformation issue. I am using the d3marchingsquares.isobands software and I needed to transpose my data prior to sending it into marching squares.
My project is create a map similar to https://earth.nullschool.net/ but using Google Maps.
I am adapting Roger Veciana's "Isobands from a geotiff with d3" demo http://bl.ocks.org/rveciana/de0bd586eafd7fcdfe29227ccbdcd511. This is almost complete but having issues resizing the canvas layer. Next I am going to overlay Danny Cochran's windable canvas layer over the temperature contours. I updated Danny Cochran's code to work with the latest Google Map's version.

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

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

Raphael JS : how to move/animate a path object?

Somehow this doesn't work...
var paper = Raphael("test", 500, 500);
var testpath = paper.path('M100 100L190 190');
var a = paper.rect(0,0,10,10);
a.attr('fill', 'silver');
a.mousedown( function() {
testpath.animate({x: 400}, 1000);
});
I can move rects this way but not paths, why is that, and how do I move a path object then?!
With the latest version of Raphael, you can do this:
var _transformedPath = Raphael.transformPath('M100 100L190 190', 'T400,0');
testpath.animate({path: _transformedPath}, 1000);
This saves you from the trouble of having to clone a temp object.
It seems a path object doesn't get a x,y value - so your animation probably still runs, but does nothing. Try instead animating the path function:
testpath.animate({path:'M400 100L490 190'},1000);
It makes it a bit trickier to write the animation, but you have the benefit of getting rotation and scaling for free!
BTW: I'm sure this is just an example, but in your above code testpath gets put in the global scope because you don't initialize as var testpath
Solved, with thanx to Rudu!
You need to create a new path to animate to. You can do this with clone() and then apply the transformations to that clone. Seems very complex for a simple move like this, but it works...
var paper = Raphael("test", 500, 500);
var testpath = paper.path('M100 100L190 190');
var a = paper.rect(0,0,10,10);
a.attr('fill', 'silver');
a.mousedown( function() {
var temp = testpath.clone();
temp.translate(400,0);
testpath.animate({path: temp.attr('path')}, 1000);
temp.remove();
});
TimDog answer was best solution.
In addition, just remember, transform string in this case means, that it will add 400 points to every path point/line X coordinate, and 0 points to every Y coordinate.
That means, M100 100L190 190 will turn into M500 100L590 190.
So, if you need to move a path element to another position, the difference between current position and new position coordinates should be calculated. You can use first element to do that:
var newCoordinates = [300, 200],
curPos = testpath.path[0],
newPosX = newCoordinates[0] - curPos[1],
newPosY = newCoordinates[1] - curPos[2];
var _transformedPath = Raphael.transformPath(testpath.path, "T"+newPosX+","+newPosY);
testpath.animate({path: _transformedPath});
Hope this will help someone.
Here's some code that generalises the best of the above answers and gives Raphael paths a simple .attr({pathXY: [newXPos, newYPos]}) attribute similar to .attr({x: newXPosition}) and .animate({x: newXPosition}) for shapes.
This lets you move your path to a fixed, absolute position or move it by a relative amount in a standard way without hardcoding path strings or custom calculations.
Edit: Code below works in IE7 and IE8. An earlier version of this failed in IE8 / VML mode due to a Raphael bug that returns arrays to .attr('path') in SVG mode but strings to .attr('path') in VML mode.
Code
Add this code (Raphael customAttribute, and helper function) after defining paper, use as below.
paper.customAttributes.pathXY = function( x,y ) {
// use with .attr({pathXY: [x,y]});
// call element.pathXY() before animating with .animate({pathXY: [x,y]})
var pathArray = Raphael.parsePathString(this.attr('path'));
var transformArray = ['T', x - this.pathXY('x'), y - this.pathXY('y') ];
return {
path: Raphael.transformPath( pathArray, transformArray)
};
};
Raphael.st.pathXY = function(xy) {
// pass 'x' or 'y' to get average x or y pos of set
// pass nothing to initiate set for pathXY animation
// recursive to work for sets, sets of sets, etc
var sum = 0, counter = 0;
this.forEach( function( element ){
var position = ( element.pathXY(xy) );
if(position){
sum += parseFloat(position);
counter++;
}
});
return (sum / counter);
};
Raphael.el.pathXY = function(xy) {
// pass 'x' or 'y' to get x or y pos of element
// pass nothing to initiate element for pathXY animation
// can use in same way for elements and sets alike
if(xy == 'x' || xy == 'y'){ // to get x or y of path
xy = (xy == 'x') ? 1 : 2;
var pathPos = Raphael.parsePathString(this.attr('path'))[0][xy];
return pathPos;
} else { // to initialise a path's pathXY, for animation
this.attr({pathXY: [this.pathXY('x'),this.pathXY('y')]});
}
};
Usage
For absolute translation (move to fixed X,Y position) - Live JSBIN demo
Works with any path or set of paths including sets of sets (demo). Note that since Raphael sets are arrays not groups, it moves each item in the set to the defined position - not the centre of the set.
// moves to x=200, y=300 regardless of previous transformations
path.attr({pathXY: [200,300]});
// moves x only, keeps current y position
path.attr({pathXY: [200,path.pathXY('y')]});
// moves y only, keeps current x position
path.attr({pathXY: [path.pathXY('x'),300]});
Raphael needs to handle both x and y co-ordinates together in the same customAttribute so they can animate together and so they stay in sync with each other.
For relative translation (move by +/- X,Y) - Live JSBIN demo
// moves down, right by 10
path.attr({pathXY: [ path.pathXY('x')+10, path.pathXY('y')+10 ]},500);
This also works with sets, but again don't forget that Raphael's sets aren't like groups - each object moves to one position relative to the average position of the set, so results may not be what are expected (example demo).
For animation (move a path to relative or absolute positions)
Before animating the first time, you need to set the pathXY values, due to a bug/missing feature up to Raphael 2.1.0 where all customAttributes need to be given a numeric value before they are animated (otherwise, they will turn every number into NaN and do nothing, failing silently with no errors, or not animating and jumping straight to the final position).
Before using .animate({pathXY: [newX,newY]});, run this helper function:
somePath.pathXY();
Yet another way is to use "transform" attribute:
testpath.animate({transform: "t400,0"}, 1000);
to move the path to the right by 400px, relative to the original position.
This should work for all shapes, including paths and rectangles.
Note that:
"transform" attribute is independent of x, y, cx, cy, etc. So these attributes are not updated by the animation above.
The value of "transform" attribute is always based on the original position, not the current position. If you apply the animation below after the animation above, it will move it 800px to the left relatively, instead of moving it back to its original position.
testpath.animate({transform: "t-400,0"}, 1000);

Categories

Resources