I've been struggling with 3D CSS lately.
I have a cube (made as taught in this tutorial: http://desandro.github.io/3dtransforms/docs/cube.html)
I'm trying to allow the user to rotate it by 90 degrees vertically or horizontally to show another face, so I have two javascript functions:
document.getElementById('rot-x').addEventListener( 'click', function()
{
rotX+=90;
$('#cube').css("transform",
"translateZ(-100px)
rotateX("+rotX+"deg)
rotateY("+rotY+"deg)
rotateZ("+rotZ+"deg)");
console.log("X:" + rotX + "\nY:" + rotY + "\nZ:" + rotZ);
};
and
document.getElementById('rot-x').addEventListener( 'click', function()
{
rotY+=90;
$('#cube').css("transform",
"translateZ(-100px)
rotateX("+rotX+"deg)
rotateY("+rotY+"deg)
rotateZ("+rotZ+"deg)");
console.log("X:" + rotX + "\nY:" + rotY + "\nZ:" + rotZ);
};
The problem is that the rotation is local apparently, which means that the axes rotate together with the object. So if I rotate once on the X axis and once on the Y axis, that second rotation doesn't show another face, as the Y axis is now perpendicular to the screen.
I really need to know how to fix this, or find an alternate way to have the user switch cube faces
I need all of these actions to always be possible:
Show face above the current one
Show face below the current one
Show face to the left of the current one
Show face to the right of the current one
Related
I'm trying to scale and then rotate a triangle and then translate it to a given point in Snap SVG.
I want to rotate the triangle around the top of it not the center, so i can build something like a pie.
So I thought I scale first, then rotate and later translate.
var t = new Snap.Matrix();
t.scale(0.5);
t.rotate(45, bbox.cx, (bbox.cy-(bbox.h/2)));
But the scale and rotation somehow are allways a bit off.
I reused a jsfiddle I found and updated it, so you can see what I try:
http://jsfiddle.net/AGq9X/477/
Somehow the bbox.cx and bbox.cy are not in the center of the triangle.
On my local setup they are.
The strange thing is, just rotation without scaleing works fine,
but scaling and then roation always seems to be a bit off on the y axis, the triangle doesn't stays at the rotation point.
Any ideas how i can fix that?
EDIT:
Ok I found the Solution,thanks to lan, you were right, the center of scaleing is important, and
I thought it was useing the center of the object, but it was the upper left corner. I adjusted it
and now it works greate:
var bbox = obj.getBBox(); //get coords etc. of triangle object
var t = new Snap.Matrix();
var offset = (bbox.cy+(bbox.h)) - centerY; //translate Y to center,
//depends on scaleing factor (0.5 = bbox.h, 0.25 = bbox.h*2)
t.scale(0.5, 0.5, bbox.cx, (bbox.cy+(bbox.h/2))); //scale object
t.translate(0,-offset); //translate to center
t.rotate(45, bbox.cx, (bbox.cy+(bbox.h/2))); //rotate object
obj.transform(t); //apply transformation to object
EDIT2:
I wanted to know how to save transformation, so you don't need to apply them every time you use a new transformation. Ian recommended to use element.transform() like so to get the old transformations:
element.transform( element.transform() + 's2,2' )
This is slightly more complicated than one would expect, but you would be animating a matrix, which does some odd things sometimes.
Personally I would use Snaps alternate animate method Snap.animate() and not using a matrix. Set the scale first and then build your animation string.
Something like..
var triangle2 = p.select("#myShape2").transform('s0.5');
...
Snap.animate(0,90,function( val ) {
triangle2.transform('r'+ val + ',' + bbox.cx+','+(bbox.cy-(bbox.h/2))+'s0.5')
}, 2000)
jsfiddle
I am using SnapSVG to display a pretty complex image. Part of the image is a circle that I would like to rotate when the user does a click/drag.
I've been reading all evening about coordinate systems and it just isn't clicking. I read this page and tried to adapt the code to convert the screen location of the click to my SVG coordinates.
Here is the code I'm trying...
function dragStart(x, y, event) {
var inverse = event.srcElement.getScreenCTM().inverse();
var point = svg.createSVGPoint();
point.x = event.clientX;
point.y = event.clientY;
var final = point.matrixTransform(inverse);
console.log('Before: (' + point.x + ', ' + point.y + ')');
console.log('Final: (' + final.x + ', ' + final.y + ')');
}
The odd thing is that the X value is returning exactly what I would expect. But the Y value makes no sense at all.
EDIT: Fiddle added here
I've stripped the SVG down to the bare minimum. The black outer ring is static. The white inner ring will rotate when the user clicks and drags. The SVG was created so that the center of the ring is at (0,0) and the SVG has a width and height of 600. Open your browser's developer tools console and click on the image. As you can see, when you click close to the center of the red circle the x coordinate is near zero as expected. But the Y value is way off.
I want to make chart, that you can not drag out of its svg element.
I'm doing this at the moment like this jsfiddle
As you can see, you can zoom and drag this freely. What i want is this:
If you drag it for example to the right and the y axis hits the edge of your screen on the left it should stop and not be able to be dragged anymore to the right.
Which also means, that you can't drag it around while not zoomed in, because it already fills its svg area.
I guess i have to somehow restrict my redraw method. At the moment it's just this
function redraw() {
plotChart.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
};
It probably has to check if for example the left edge of the chart hits coordinate [0][x] and then somehow stop drawing it any further out.
To know the axis point on scaling and on translation you need to get the CTM and then transform to get the real points.
//gets CTM on the first y line that is the group
var ctm = d3.select(".x").select(".tick")[0][0].getCTM();
//the first y line
var k = d3.select("svg")[0][0].createSVGPoint();
//current point without transform
k.x = d3.select(".x").select("line").attr("x2");
k.y = d3.select(".x").select("line").attr("y2")
j = k.matrixTransform(ctm)
//j is the real point of the line post transform.
//your code to zoom or pan depending on the value of j
I am working on some image viewing tools in KineticJS. I have a rotate tool. When you move the mouse over an image, a line appears from the centre of the image to the mouse position, and then when you click and move, the line follows and the image rotates around that point, in sync with the line. This works great.
My issue is, I have the following set up:
Canvas->
Stage->
Layer->
GroupA->
GroupB->
Image
This is because I draw tabs for options on GroupA and regard it as a container for the image. GroupB is used because I flip GroupB to flip the image ( and down the track, any objects like Text and Paths that I add to the image ), so that it flips but stays in place. This all works well. I am also hoping when I offer zoom, to zoom groupb and thus zoom anything drawn on the image, but for groupA to create clipping and continue to support drag buttons, etc.
The object I am rotating, is GroupA. Here is the method I call to set up rotation:
this.init = function(control)
{
console.log("Initing rotate for : " + control.id());
RotateTool.isMouseDown = false;
RotateTool.startRot = isNaN(control.getRotationDeg()) ? 0 : control.getRotationDeg();
RotateTool.lastAngle = control.parent.rotation() / RotateTool.factor;
RotateTool.startAngle = control.parent.rotation();
this.image = control.parent;
var center = this.getCentrePoint();
RotateTool.middleX = this.image.getAbsolutePosition().x + center.x;
RotateTool.middleY = this.image.getAbsolutePosition().y + center.y;
this.image.x(this.image.x() + center.x - this.image.offsetX());
this.image.y(this.image.y() + center.y - this.image.offsetY());
this.image.offsetX(center.x);
this.image.offsetY(center.y);
}
getCentrePoint is a method that uses trig to get the size of the image, based on the rotation. As I draw a line to the centre of the image, I can tell it's working well, to start with. I've also stepped in to it and it always returns values only slightly higher than the actual width and height, they always look like they should be about what I'd expect, for the angle of the image.
Here is the code I use on mouse move to rotate the image:
this.layerMouseMove = function (evt, layer)
{
if (RotateTool.isRotating == false)
return;
if (!Util.isNull(this.image) && !Util.isNull(this.line))
{
if (Item.moving && !RotateTool.isRotating)
{
console.log("layer mousemove item moving");
RotateTool.layerMouseUp(evt, layer);
}
else
{
var pt = this.translatePoint(evt.x, evt.y);
var x = pt.x;
var y = pt.y;
var end = this.getPoint(x, y, .8);
RotateTool.line.points([this.middleX, this.middleY, end.x, end.y]);
RotateTool.line.parent.draw();
RotateTool.sign.x(x - 20);
RotateTool.sign.y(y - 20);
var angle = Util.findAngle({ x: RotateTool.startX, y: RotateTool.startY }, { x: x, y: y }, { x: RotateTool.middleX, y: RotateTool.middleY });
var newRot = (angle) + RotateTool.startAngle;
RotateTool.image.rotation(newRot);
console.log(newRot);
}
}
}
Much of this code is ephemeral, it's maintaining the line ( which is 80% of the length from the centre to my mouse, as I also show a rotate icon, over the mouse.
Sorry for the long windedness, I'm trying to make sure I am clear, and that it's obvious that I've done a lot of work before asking for help.
So, here is my issue. After I've rotated a few times, when I click again, the 'centre' point that the line draws to, is way off the bottom right of my screen, and if I set a break point, sure enough, the absolute position of my groups are no longer in sync. This seems to me like my rotation has moved the image in the manner I hoped, but moved my group off screen. When I set offsetX and offsetY, do I need to also set it on all the children ? But, it's the bottom child I can see, and the top group I set those things on, so I don't really understand how this is happening.
I do notice my image jumps a few pixels when I move the mouse over it ( which is when the init method is called ) so I feel like perhaps I am just out slightly somewhere, and it's causing this flow on effect. I've done some more testing, and my image always jumps slightly up and to the right when I move the mouse over it, and the rotate tool continues to work reliably, so long as I don't move the mouse over the image again, causing my init method to call. It seems like every time this is called, is when it breaks. So, I could just call it once, but I'd have to associate the data with the image then, for the simple reason that once I have many images, I'll need to change my data as my selected image changes. Either way, I'd prefer to understand and resolve the issue, than just hide it.
Any help appreciated.
For example it may be used in the application of manually adjusting the hands of the clock. I guess it probably involves translating the needle (to make the end point of the needle the centre of rotation) then rotating it, then translating the needle again.
But since the needle listens to the mouse event all the time, the 1st mouse event will be captured. The result is that the needle ends up being translated and not rotated at all. Mouse event is impossible to debug too...
Any idea or code snippets that I can refer to? Using Javascript or CSS to rotate both fine.
In your example, you will want to calculate the angle between the centre of the clock face (black dot), and the current mouse position (red dot), relative to the Y axis (cardinal north if you imagine a compass).
If I remember my trig correctly, you can calculate this by using the following:
var angle = Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI;
// alter the angle to be relative to Y axis instead of X
angle += 90;
if(angle < 0) { angle = 360 + angle; }
In the formula, x and y are the coordinates of the two points, one of which you will know (it is the centre of the clock face), and the other you can get from the mouse move event.
Once you have the angle, you can simply translate to the the centre of the circle, rotate the canvas by the calculated amount, and draw the hand.
Update: I created a jsfiddle to illustrate the angle calculation:
http://jsfiddle.net/DAEpy/1/