Raphael SVG VML Implement Multi Pivot Points for Rotation - javascript

Over the last two days I've effectively figured out how NOT to rotate Raphael Elements.
Basically I am trying to implement a multiple pivot points on element to rotate it by mouse.
When a user enters rotation mode 5 pivots are created. One for each corner of the bounding box and one in the center of the box.
When the mouse is down and moving it is simple enough to rotate around the pivot using Raphael elements.rotate(degrees, x, y) and calculating the degrees based on the mouse positions and atan2 to the pivot point.
The problem arises after I've rotated the element, bbox, and the other pivots. There x,y position in the same only there viewport is different.
In an SVG enabled browser I can create new pivot points based on matrixTransformation and getCTM. However after creating the first set of new pivots, every rotation after the pivots get further away from the transformed bbox due to rounding errors.
The above is not even an option in IE since in is VML based and cannot account for transformation.
Is the only effective way to implement
element rotation is by using rotate
absolute or rotating around the center
of the bounding box?
Is it possible at all the create multi
pivot points for an object and update
them after mouseup to remain in the
corners and center of the transformed
bbox?
UPDATE:
I've attempted to use jQuery offset to find the pivot after it's been rotated, and to use that offset location as the pivot point.
Demo site ...
http://weather.speedfetishperformance.com/dev/raphael/rotation.html

The best cross-browser way I can think of to do what you want is to implement the rotation yourself rather than let SVG do it. Rotating x,y coordinates is fairly simple and I've been using this (tcl) code whenever I need to do 2D rotation: Canvas Rotation.
The upside to this is you have maximum control of the rotation since you're doing it manually. This solves the problems you're having trying to guess the final coordinates after rotation. Also, this should be cross browser compatible.
The downside is you have to use paths. So no rects (though it should be easy to convert them to paths) or ellipses (a little bit harder to convert to path but doable). Also, since you're doing it manually, it should be slower than letting SVG do it for you.
Here's a partial implementation of that Tcl code in javascript:
first we need a regexp to tokenize SVG paths:
var svg_path_regexp = (function(){
var number = '-?[0-9.]+';
var comma = '\s*[, \t]\s*';
var space = '\s+';
var xy = number + comma + number;
var standard_paths = '[mlcsqt]';
var horiz_vert = '[hv]\s*' + number;
var arc = 'a\s*' + xy + space + number + space + xy + space + xy;
var OR = '\s*|';
return new RegExp(
standard_paths +OR+
xy +OR+
horiz_vert +OR+
arc,
'ig'
);
})();
now we can implement the rotate function:
function rotate_SVG_path (path, Ox, Oy, angle) {
angle = angle * Math.atan(1) * 4 / 180.0; // degrees to radians
var tokens = path.match(svg_path_regexp);
for (var i=0; i<tokens.length; i++) {
var token = tokens[i].replace(/^\s+|\s+$/g,''); // trim string
if (token.match(/\d/)) { // assume it's a coordinate
var xy = token.split(/[, \t]+/);
var x = parseFloat(xy[0]);
var y = parseFloat(xy[1]);
x = x - Ox; // Shift to origin
y = y - Oy;
var xx = x * Math.cos(angle) - y * Math.sin(angle); // Rotate
var yy = x * Math.sin(angle) + y * Math.cos(angle);
x = xx + Ox; // Shift back
y = yy + Oy;
token = x + ',' + y;
}
else if (token.match(/^[hv]/)) {
// handle horizontal/vertical line here
}
else if (token.match(/^a/)) {
// handle arcs here
}
tokens[i] = token;
}
return tokens.join('');
}
The above rotate function implements everything except horizontal/vertical lines (you need to keep track of previous xy value) and arcs. Neither should be too hard to implement.

Related

p5js - Radial lines of different lengths not drawing correctly

I'm working on a p5js project, and drawing radial lines with different lengths. I'm using this snippet in a loop to map and draw the lines:
var x1 = (this.mapR1)*Math.cos(i*2*Math.PI/this.numberLines);
var y1 = (this.mapR1)*Math.sin(i*2*Math.PI/this.numberLines);
var x2 = (this.mapR1 + this.mapRMinLen + (plot*this.mapRMaxLen*shift))*Math.cos(i*2*Math.PI/this.numberLines + skew);
var y2 = (this.mapR1 + this.mapRMinLen + (plot*this.mapRMaxLen*shift))*Math.sin(i*2*Math.PI/this.numberLines + skew)
p5.line(this.lines[i].x1, this.lines[i].y1, this.lines[i].x2, this.lines[i].y2);;
Plot is a simple bell curve distribution (between 0 and 1) to make the lines smoothly change length and shift is a randomly generated number to randomly scatter the second radius and change the length.
Skew is where things go awry. With skew I'm angling the lines from the center. Without shift, skew works great, but once I scatter the length of the lines, the math to calculate the position of the outer radius gets... well, skewed (image attached).
Here's a link to a codepen where you can see what I mean: https://codepen.io/chazthetic/pen/KKQNKaM. Is there a better way to calculate the second point position where the lines will be lined up?
One idea is to base the calculation of x2 and y2 on x1 and y1; sort of use them as the jumping off point for the calculating the end of the line segment.
Meaning:
var x2 = x1 + (rMinLen + (plot*rMaxLen*shift))*Math.cos(theta + skew);
var y2 = y1 + (rMinLen + (plot*rMaxLen*shift))*Math.sin(theta + skew);
https://codepen.io/lecrte/pen/JjpbwOV

Three.js position objects on curve - rotate towards center of circle

I'm attempting to create a snazzy VR menu so that when the user looks down the menu items are in a column below camera curved around circle so they look the same and are rotated towards camera.
Here is my attempt thus far
And a CodePen with the example
http://codepen.io/bknill/pen/BLOwLj?editors=0010
I'm using some code I found that calculates the position
var radius = 60; // radius of the circle
var height = 60,
angle = 0,
step = (Math.PI /2 ) / menuItems.length;
menuItems.forEach(function(item,index){
var menuItem = createMenuItem(item.title);
menuItem.position.y = - Math.round(height/2 + radius * Math.sin(angle));
menuItem.position.z = Math.round(height/2 + radius * Math.sin(angle));
// menuItem.rotation.x = -Math.round(Math.PI * Math.sin(angle));
angle += step;
menu.add(menuItem);
})
Which is almost right, the next stage is to get them to rotate in a uniform way towards the camera. Using menuItem.lookAt(camera.position) isn't working - they're not uniform rotation.
child.lookAt(camera.position.normalize()) does this
Anyone let me know the clever maths I need to get the rotation of the item so they face the camera and look like they're on a curve?
The simplest way to have an object facing another object is using lookAt.
Be aware this method needs a direction vector, not the point where you want it to look at.
(position you want to look at - position of your object).normalize();
In your case:
var dirToLookAt = new THREE.Vector3();
dirToLookAt.subVectors(menuItem.position, camera.position);
// could be: dirToLookAt.subVectors(camera.position, menuItem.position);
dirToLookAt.normalize();
Operations based on Vector3 documentation.
For more info you can read the last discussion I had about this method.

Javascript, Math: calculate the area of a flat, 2D surface that is situated in 3D

I want to be able to calculate the surface area of a 2D polygon of any shape, given a set of 3D vertices. For example, what is the surface area of this figure?
var polygon = new Polygon([new Point(0,0,0), new Point(5,8,2), new Point(11,15,7)])
polygon.areaIfPolygonIs3D()
--> some predictable result, no matter how many vertices the polygon has...
Keep in mind that polygons only have one surface. They are flat but could be triangle shaped or trapezoid shaped or randomly shaped, and could be floating at a 3D angle... imagine them as pieces of paper turned any which way in 3D space.
What I've tried to do so far is rotate the thing flat, and then use a basic formula for calculating the area of a 2D irregular polygon which is currently working in my code (formula: http://www.wikihow.com/Calculate-the-Area-of-a-Polygon). I had such a hard figuring out how to rotate all the vertices so the polygon lays flat (all "z" values are 0) that I abandoned that path, though I'm open to trying it if someone can get there. (Perhaps there is a bug in Point.rotateBy().)
I can work with Points, and Edges (created with point.to(point)), and Edges have 'theta' (edge.theta()) and 'phi' (edge.phi()).
In any case, if someone can fill in what goes here and help me after a full days effort of trying to relearn all the geometry I forgot from high school, that would be much appreciated!
var locatorRho = function(x,y,z) {
return Math.sqrt(x*x + y*y + z*z);
}
var locatorTheta = function(x,y) {
return Math.atan2(y,x);
};
var locatorPhi = function(x,y,z) {
return z == 0 ? Math.PI_2 : Math.acos(z/locatorRho(x, y, z));
}
// rotates a point according to another point ('locator'), and their 2D angle ('theta') and 3D angle ('phi')
Point.prototype.rotateBy = function(locator, theta, phi) {
phi = (phi == undefined ? 0 : phi);
var relativeX = this.x() - locator.x();
var relativeY = this.y() - locator.y();
var relativeZ = this.z() - locator.z();
var distance = locatorRho(relativeX, relativeY, relativeZ);
var newTheta = locatorTheta(relativeX, relativeY) + theta;
var newPhi = locatorPhi(relativeX, relativeY, relativeZ) + phi;
this._x = locatorX(distance, newTheta, newPhi) + locator.x();
this._y = locatorY(distance, newTheta, newPhi) + locator.y();
this._z = locatorZ(distance, newPhi) + locator.z();
}
Polygon.prototype.signedArea = function() {
var vertices = this.vertices();
var area = 0;
for(var i=0, j=1, length=vertices.length; i<length; ++i, j=(i+1)%length) {
area += vertices[i].x()*vertices[j].y() - vertices[j].x()*vertices[i].y();
}
return 0.5*area
}
Polygon.prototype.areaIfPolygonIs2D = function() {
return Math.abs(rotatedFlatCopy.signedArea())
}
Polygon.prototype.areaIfPolygonIs3D = function() {
... help here I am so stuck ...
}
var vertices = [some number of Points, e.g., new Point(x,y,z)]
var polygon = new Polygon(vertices)
var polygon.areaIfPolygonIs3D()
--> result
If your polygon plane is not parallel to Z axis, you can calculate area projection with known approach using X and Y coordinates only, then divide result by cosine of angle between Z axis and normal N to that plane
Area = Sum[x1*y2-x2*y1 +...] ////shoelace formula
True_Area = Area / Cos(Angle between N and Z axis)) =
Area / DotProduct((N.x,N.y,N.z), (0,0,1)) =
Area / N.z
//// if N is normalized (unit)
Use the shoelace formula three times, on the 2D vertices (X, Y), (Y, Z) and (Z, X). The desired area is given by √Axy²+Ayz²+Azx² (provided the polygon is flat).

Place rectangles randomly but not within x radius in the center

How can I place rectangles with variable width and height, randomly in a stage but away from a circle in the center which has radius of x
Thanks in advance
EDIT
check my code so far
http://jsfiddle.net/chchrist/cAShH/1/
The three potential options I would follow are:
Generate random coordinates in [400,400] and then check that the distance from [200,200] is less than 50. If it is, fine; if not, start again.
Generate random polar coordinates (i.e., angle and distance), where the distance is greater than 50. Then convert these to Cartesian, centred around [200,200] and bounded to your area... The problem with this approach is that it would introduce bias at the extremities of your rectangular area.
Ignore the circle and bound it by a square, then use the first approach but with simplified logic.
One approach might be to think about how to map uniform random numbers into legal positions.
For example (simplifying slightly), if you had a 200 x 200 square, and you wanted to avoid any points in a 100x100 square in the middle, you could do the following for each coordinate. Generate a random number between 0 and 100. If it's less than 50, use it directly; otherwise add 100 to it (to put it in the 150-200 range)
Conceptually this stretches the range around the "hole" in the middle, while still leaving the resulting points uniformly distributed.
It'll be trickier with your circle, as the axes are not independent, but a variation on this method could be worth considering. (Especially if you only have "soft" requirements for randomness and so can relax the constraints on the distribution somewhat).
I would start with a coordinate system centered at 0,0 and after you have generated valid coordinates map them onto your square/rectangle.
Here's a simple example:
function getValidCoordinates() {
var x, y, isValid = false;
while (!isValid) {
x = Math.random() * 400 - 200;
y = Math.random() * 400 - 200;
if (Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)) > 50)
isValid = true;
//else alert('too close ' + x + ',' + y);
}
return {x: x + 200, y: y + 200};
}
for (var i=0; i < 10; i++) {
var co = getValidCoordinates();
alert('x=' + co.x + ', y=' + co.y);
}

Finding point n% away from the centre of a semicircle in Javascript?

I'm sorry to say that Math really isn't my strong suit. Normally I can get by, but this has got me totally stumped.
I'm trying to code up a quiz results screen in HTML/CSS/Javascript.
On my interface, I have a semicircle (the right hemisphere of a target).
I have a range of 'scores' (integers out of 100 - so 50, 80, 90 etc.).
I need to plot these points on the semicircle to be n% away from the centre, where n is the value of each score - the higher the score, the closer to the centre of the target the point will appear.
I know how wide my semicircle is, and have already handled the conversion of the % values so that the higher ones appear closer to the centre while the lower ones appear further out.
What I can't wrap my head around is plotting these points on a line that travels out from the centre point (x = 0, y = target height/2) of the target at a random angle (so the points don't overlap).
Any suggestions are gratefully received!
Do you have an example of what you want this to look like? It sounds like you want to divide up the circle into N slices where N is the number of points you need to display, then plot the points along each of those radii. So you might have something like:
Edit: code was rotating about the origin, not the circle specified
var scores = [];
//...
//assume scores is an array of distances from the center of the circle
var points = [];
var interval = 2 * Math.PI / N;
var angle;
for (var i = 0; i < N; i++) {
angle = interval * i;
//assume (cx, cy) are the coordinates of the center of your circle
points.push({
x: scores[i] * Math.cos(angle) + cx,
y: scores[i] * Math.sin(angle) + cy
});
}
Then you can plot points however you see fit.
After much headscratching, I managed to arrive at this solution (with the help of a colleague who's much, much better at this kind of thing than me):
(arr_result is an array containing IDs and scores - scores are percentages of 100)
for (var i = 0; i < arr_result.length; i++){
var angle = angleArray[i]; // this is an array of angles (randomised) - points around the edge of the semicircle
var radius = 150; // width of the semicircle
var deadZone = 25 // to make matters complicated, the circle has a 'dead zone' in the centre which we want to discount
var maxScore = 100
var score = parseInt(arr_result[i]['score'], 10)
var alpha = angle * Math.PI
var distance = (maxScore-score)/maxScore*(radius-deadZone) + deadZone
var x = distance * Math.sin(alpha)
var y = radius + distance * Math.cos(alpha)
$('#marker_' + arr_result[i]['id'], templateCode).css({ // target a specific marker and move it using jQuery
'left' : pointX,
'top': pointY
});
}
I've omitted the code for generating the array of angles and randomising that array - that's only needed for presentational purposes so the markers don't overlap.
I also do some weird things with the co-ordinates before I move the markers (again, this has been omitted) as I want the point to be at the bottom-centre of the marker rather than the top-left.

Categories